/*
 This file 'avalon2' is part of Firebird Integrated Solution 1.0

 Copyright (c) 2018 Lincong

 Contact:  
        Email: lincong1987@gmail.com

        QQ: 159257119
 
 See Usage at http://www.jplatformx.com/firebird

 Create date: 2018-05-28 16:04
 */





/*!
built in 2018-2-7:20:17 version 2.2.10 by 司徒正美
https://github.com/RubyLouvre/avalon/tree/2.2.9
修复ms-for循环生成option与ms-deplex的联动问题
解决 IE8 html 属性中的中文被转成 unicode 字符串问题
修复多个计算属性不更新的问题

*/(function (global, factory) {


	define(function (require, exports, module) {
		module.exports = factory();
	});

	// typeof exports === 'object' && typeof module !== 'undefined'
	// 	? module.exports = factory() : typeof define === 'function' && define.amd
	// 	? define(factory) : global.avalon = factory();

})(this, function () {
	'use strict';

	var win = typeof window === 'object' ? window : typeof global === 'object' ? global : {};

	var inBrowser = !!win.location && win.navigator;
	/* istanbul ignore if  */

	var document$1 = inBrowser ? win.document : {
		createElement: Object,
		createElementNS: Object,
		documentElement: 'xx',
		contains: Boolean
	};
	var root = inBrowser ? document$1.documentElement : {
		outerHTML: 'x'
	};

	var versions = {
		objectobject: 7, //IE7-8
		objectundefined: 6, //IE6
		undefinedfunction: NaN, // other modern browsers
		undefinedobject: NaN //Mobile Safari 8.0.0 (iOS 8.4.0)
		//objectfunction chrome 47
	};
	/* istanbul ignore next  */
	var msie$1 = document$1.documentMode || versions[typeof document$1.all + typeof XMLHttpRequest];

	var modern = /NaN|undefined/.test(msie$1) || msie$1 > 8;

	/*
     https://github.com/rsms/js-lru
     entry             entry             entry             entry
     ______            ______            ______            ______
     | head |.newer => |      |.newer => |      |.newer => | tail |
     |  A   |          |  B   |          |  C   |          |  D   |
     |______| <= older.|______| <= older.|______| <= older.|______|

     removed  <--  <--  <--  <--  <--  <--  <--  <--  <--  <--  <--  added
     */
	function Cache(maxLength) {
		// 标识当前缓存数组的大小
		this.size = 0;
		// 标识缓存数组能达到的最大长度
		this.limit = maxLength;
		//  head（最不常用的项），tail（最常用的项）全部初始化为undefined

		this.head = this.tail = void 0;
		this._keymap = {};
	}

	Cache.prototype = {
		put: function put(key, value) {
			var entry = {
				key: key,
				value: value
			};
			this._keymap[key] = entry;
			if (this.tail) {
				// 如果存在tail（缓存数组的长度不为0），将tail指向新的 entry
				this.tail.newer = entry;
				entry.older = this.tail;
			} else {
				// 如果缓存数组的长度为0，将head指向新的entry
				this.head = entry;
			}
			this.tail = entry;
			// 如果缓存数组达到上限，则先删除 head 指向的缓存对象
			/* istanbul ignore if */
			if (this.size === this.limit) {
				this.shift();
			} else {
				this.size++;
			}
			return value;
		},
		shift: function shift() {
			/* istanbul ignore next */
			var entry = this.head;
			/* istanbul ignore if */
			if (entry) {
				// 删除 head ，并改变指向
				this.head = this.head.newer;
				// 同步更新 _keymap 里面的属性值
				this.head.older = entry.newer = entry.older = this._keymap[entry.key] = void 0;
				delete this._keymap[entry.key]; //#1029
				// 同步更新 缓存数组的长度
				this.size--;
			}
		},
		get: function get(key) {
			var entry = this._keymap[key];
			// 如果查找不到含有`key`这个属性的缓存对象
			if (entry === void 0) return;
			// 如果查找到的缓存对象已经是 tail (最近使用过的)
			/* istanbul ignore if */
			if (entry === this.tail) {
				return entry.value;
			}
			// HEAD--------------TAIL
			//   <.older   .newer>
			//  <--- add direction --
			//   A  B  C  <D>  E
			if (entry.newer) {
				// 处理 newer 指向
				if (entry === this.head) {
					// 如果查找到的缓存对象是 head (最近最少使用过的)
					// 则将 head 指向原 head 的 newer 所指向的缓存对象
					this.head = entry.newer;
				}
				// 将所查找的缓存对象的下一级的 older 指向所查找的缓存对象的older所指向的值
				// 例如：A B C D E
				// 如果查找到的是D，那么将E指向C，不再指向D
				entry.newer.older = entry.older; // C <-- E.
			}
			if (entry.older) {
				// 处理 older 指向
				// 如果查找到的是D，那么C指向E，不再指向D
				entry.older.newer = entry.newer; // C. --> E
			}
			// 处理所查找到的对象的 newer 以及 older 指向
			entry.newer = void 0; // D --x
			// older指向之前使用过的变量，即D指向E
			entry.older = this.tail; // D. --> E
			if (this.tail) {
				// 将E的newer指向D
				this.tail.newer = entry; // E. <-- D
			}
			// 改变 tail 为D
			this.tail = entry;
			return entry.value;
		}
	};

	var delayCompile = {};

	var directives = {};

	function directive(name, opts) {
		if (directives[name]) {
			avalon.warn(name, 'directive have defined! ');
		}
		directives[name] = opts;
		if (!opts.update) {
			opts.update = function () {};
		}
		if (opts.delay) {
			delayCompile[name] = 1;
		}
		return opts;
	}

	function delayCompileNodes(dirs) {
		for (var i in delayCompile) {
			if ('ms-' + i in dirs) {
				return true;
			}
		}
	}

	var window$1 = win;
	function avalon(el) {
		return new avalon.init(el);
	}

	avalon.init = function (el) {
		this[0] = this.element = el;
	};

	avalon.fn = avalon.prototype = avalon.init.prototype;

	function shadowCopy(destination, source) {
		for (var property in source) {
			destination[property] = source[property];
		}
		return destination;
	}
	var rword = /[^, ]+/g;
	var rnowhite = /\S+/g; //存在非空字符
	var platform = {}; //用于放置平台差异的方法与属性


	function oneObject(array, val) {
		if (typeof array === 'string') {
			array = array.match(rword) || [];
		}
		var result = {},
			value = val !== void 0 ? val : 1;
		for (var i = 0, n = array.length; i < n; i++) {
			result[array[i]] = value;
		}
		return result;
	}

	var op = Object.prototype;
	function quote(str) {
		return avalon._quote(str);
	}
	var inspect = op.toString;
	var ohasOwn = op.hasOwnProperty;
	var ap = Array.prototype;

	var hasConsole = typeof console === 'object';
	avalon.config = { debug: true };
	function log() {
		if (hasConsole && avalon.config.debug) {
			Function.apply.call(console.log, console, arguments);
		}
	}
	function warn() {
		if (hasConsole && avalon.config.debug) {
			var method = console.warn || console.log;
			// http://qiang106.iteye.com/blog/1721425
			Function.apply.call(method, console, arguments);
		}
	}
	function error(str, e) {
		throw (e || Error)(str);
	}
	function noop() {}
	function isObject(a) {
		return a !== null && typeof a === 'object';
	}

	function range(start, end, step) {
		// 用于生成整数数组
		step || (step = 1);
		if (end == null) {
			end = start || 0;
			start = 0;
		}
		var index = -1,
			length = Math.max(0, Math.ceil((end - start) / step)),
			result = new Array(length);
		while (++index < length) {
			result[index] = start;
			start += step;
		}
		return result;
	}

	var rhyphen = /([a-z\d])([A-Z]+)/g;
	function hyphen(target) {
		//转换为连字符线风格
		return target.replace(rhyphen, '$1-$2').toLowerCase();
	}

	var rcamelize = /[-_][^-_]/g;
	function camelize(target) {
		//提前判断，提高getStyle等的效率
		if (!target || target.indexOf('-') < 0 && target.indexOf('_') < 0) {
			return target;
		}
		//转换为驼峰风格
		return target.replace(rcamelize, function (match) {
			return match.charAt(1).toUpperCase();
		});
	}

	var _slice = ap.slice;
	function slice(nodes, start, end) {
		return _slice.call(nodes, start, end);
	}

	var rhashcode = /\d\.\d{4}/;
	//生成UUID http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript
	function makeHashCode(prefix) {
		/* istanbul ignore next*/
		prefix = prefix || 'avalon';
		/* istanbul ignore next*/
		return String(Math.random() + Math.random()).replace(rhashcode, prefix);
	}
	//生成事件回调的UUID(用户通过ms-on指令)
	function getLongID(fn) {
		/* istanbul ignore next */
		return fn.uuid || (fn.uuid = makeHashCode('e'));
	}
	var UUID = 1;
	//生成事件回调的UUID(用户通过avalon.bind)
	function getShortID(fn) {
		/* istanbul ignore next */
		return fn.uuid || (fn.uuid = '_' + ++UUID);
	}

	var rescape = /[-.*+?^${}()|[\]\/\\]/g;
	function escapeRegExp(target) {
		//http://stevenlevithan.com/regex/xregexp/
		//将字符串安全格式化为正则表达式的源码
		return (target + '').replace(rescape, '\\$&');
	}

	var eventHooks = {};
	var eventListeners = {};
	var validators = {};
	var cssHooks = {};

	window$1.avalon = avalon;

	function createFragment() {
		/* istanbul ignore next  */
		return document$1.createDocumentFragment();
	}

	var rentities = /&[a-z0-9#]{2,10};/;
	var temp = document$1.createElement('div');
	shadowCopy(avalon, {
		Array: {
			merge: function merge(target, other) {
				//合并两个数组 avalon2新增
				target.push.apply(target, other);
			},
			ensure: function ensure(target, item) {
				//只有当前数组不存在此元素时只添加它
				if (target.indexOf(item) === -1) {
					return target.push(item);
				}
			},
			removeAt: function removeAt(target, index) {
				//移除数组中指定位置的元素，返回布尔表示成功与否
				return !!target.splice(index, 1).length;
			},
			remove: function remove(target, item) {
				//移除数组中第一个匹配传参的那个元素，返回布尔表示成功与否
				var index = target.indexOf(item);
				if (~index) return avalon.Array.removeAt(target, index);
				return false;
			}
		},
		evaluatorPool: new Cache(888),
		parsers: {
			number: function number(a) {
				return a === '' ? '' : parseFloat(a) || 0;
			},
			string: function string(a) {
				return a === null || a === void 0 ? '' : a + '';
			},
			"boolean": function boolean(a) {
				if (a === '') return a;
				return a === 'true' || a === '1';
			}
		},
		_decode: function _decode(str) {
			if (rentities.test(str)) {
				temp.innerHTML = str;
				return temp.innerText || temp.textContent;
			}
			return str;
		}
	});

	//============== config ============
	function config(settings) {
		for (var p in settings) {
			var val = settings[p];
			if (typeof config.plugins[p] === 'function') {
				config.plugins[p](val);
			} else {
				config[p] = val;
			}
		}
		return this;
	}

	var plugins = {
		interpolate: function interpolate(array) {
			var openTag = array[0];
			var closeTag = array[1];
			if (openTag === closeTag) {
				throw new SyntaxError('interpolate openTag cannot equal to closeTag');
			}
			var str = openTag + 'test' + closeTag;

			if (/[<>]/.test(str)) {
				throw new SyntaxError('interpolate cannot contains "<" or ">"');
			}

			config.openTag = openTag;
			config.closeTag = closeTag;
			var o = escapeRegExp(openTag);
			var c = escapeRegExp(closeTag);

			config.rtext = new RegExp(o + '(.+?)' + c, 'g');
			config.rexpr = new RegExp(o + '([\\s\\S]*)' + c);
		}
	};
	function createAnchor(nodeValue) {
		return document$1.createComment(nodeValue);
	}
	config.plugins = plugins;
	config({
		interpolate: ['{{', '}}'],
		debug: true
	});
	//============  config ============

	shadowCopy(avalon, {
		shadowCopy: shadowCopy,

		oneObject: oneObject,
		inspect: inspect,
		ohasOwn: ohasOwn,
		rword: rword,
		version: "2.2.10",
		vmodels: {},

		directives: directives,
		directive: directive,

		eventHooks: eventHooks,
		eventListeners: eventListeners,
		validators: validators,
		cssHooks: cssHooks,

		log: log,
		noop: noop,
		warn: warn,
		error: error,
		config: config,

		modern: modern,
		msie: msie$1,
		root: root,
		document: document$1,
		window: window$1,
		inBrowser: inBrowser,

		isObject: isObject,
		range: range,
		slice: slice,
		hyphen: hyphen,
		camelize: camelize,
		escapeRegExp: escapeRegExp,
		quote: quote,

		makeHashCode: makeHashCode

	});

	/**
	 * 此模块用于修复语言的底层缺陷
	 */
	function isNative(fn) {
		return (/\[native code\]/.test(fn)
		);
	}

	/* istanbul ignore if*/
	if (!isNative('司徒正美'.trim)) {
		var rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;
		String.prototype.trim = function () {
			return this.replace(rtrim, '');
		};
	}
	if (!Object.create) {
		Object.create = function () {
			function F() {}

			return function (o) {
				if (arguments.length != 1) {
					throw new Error('Object.create implementation only accepts one parameter.');
				}
				F.prototype = o;
				return new F();
			};
		}();
	}
	var hasDontEnumBug = !{
		'toString': null
	}.propertyIsEnumerable('toString');
	var hasProtoEnumBug = function () {}.propertyIsEnumerable('prototype');
	var dontEnums = ['toString', 'toLocaleString', 'valueOf', 'hasOwnProperty', 'isPrototypeOf', 'propertyIsEnumerable', 'constructor'];
	var dontEnumsLength = dontEnums.length;
	/* istanbul ignore if*/
	if (!isNative(Object.keys)) {
		Object.keys = function (object) {
			//ecma262v5 15.2.3.14
			var theKeys = [];
			var skipProto = hasProtoEnumBug && typeof object === 'function';
			if (typeof object === 'string' || object && object.callee) {
				for (var i = 0; i < object.length; ++i) {
					theKeys.push(String(i));
				}
			} else {
				for (var name in object) {
					if (!(skipProto && name === 'prototype') && ohasOwn.call(object, name)) {
						theKeys.push(String(name));
					}
				}
			}

			if (hasDontEnumBug) {
				var ctor = object.constructor,
					skipConstructor = ctor && ctor.prototype === object;
				for (var j = 0; j < dontEnumsLength; j++) {
					var dontEnum = dontEnums[j];
					if (!(skipConstructor && dontEnum === 'constructor') && ohasOwn.call(object, dontEnum)) {
						theKeys.push(dontEnum);
					}
				}
			}
			return theKeys;
		};
	}
	/* istanbul ignore if*/
	if (!isNative(Array.isArray)) {
		Array.isArray = function (a) {
			return Object.prototype.toString.call(a) === '[object Array]';
		};
	}

	/* istanbul ignore if*/
	if (!isNative(isNative.bind)) {
		/* istanbul ignore next*/
		Function.prototype.bind = function (scope) {
			if (arguments.length < 2 && scope === void 0) return this;
			var fn = this,
				argv = arguments;
			return function () {
				var args = [],
					i;
				for (i = 1; i < argv.length; i++) {
					args.push(argv[i]);
				}for (i = 0; i < arguments.length; i++) {
					args.push(arguments[i]);
				}return fn.apply(scope, args);
			};
		};
	}
	//https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/slice
	/**
	 * Shim for "fixing" IE's lack of support (IE < 9) for applying slice
	 * on host objects like NamedNodeMap, NodeList, and HTMLCollection
	 * (technically, since host objects have been implementation-dependent,
	 * at least before ES6, IE hasn't needed to work this way).
	 * Also works on strings, fixes IE < 9 to allow an explicit undefined
	 * for the 2nd argument (as in Firefox), and prevents errors when
	 * called on other DOM objects.
	 */

	try {
		// Can't be used with DOM elements in IE < 9
		_slice.call(avalon.document.documentElement);
	} catch (e) {
		// Fails in IE < 9
		// This will work for genuine arrays, array-like objects,
		// NamedNodeMap (attributes, entities, notations),
		// NodeList (e.g., getElementsByTagName), HTMLCollection (e.g., childNodes),
		// and will not fail on other DOM objects (as do DOM elements in IE < 9)
		/* istanbul ignore next*/
		ap.slice = function (begin, end) {
			// IE < 9 gets unhappy with an undefined end argument
			end = typeof end !== 'undefined' ? end : this.length;

			// For native Array objects, we use the native slice function
			if (Array.isArray(this)) {
				return _slice.call(this, begin, end);
			}

			// For array like object we handle it ourselves.
			var i,
				cloned = [],
				size,
				len = this.length;

			// Handle negative value for "begin"
			var start = begin || 0;
			start = start >= 0 ? start : len + start;

			// Handle negative value for "end"
			var upTo = end ? end : len;
			if (end < 0) {
				upTo = len + end;
			}

			// Actual expected size of the slice
			size = upTo - start;

			if (size > 0) {
				cloned = new Array(size);
				if (this.charAt) {
					for (i = 0; i < size; i++) {
						cloned[i] = this.charAt(start + i);
					}
				} else {
					for (i = 0; i < size; i++) {
						cloned[i] = this[start + i];
					}
				}
			}

			return cloned;
		};
	}
	/* istanbul ignore next*/
	function iterator(vars, body, ret) {
		var fun = 'for(var ' + vars + 'i=0,n = this.length; i < n; i++){' + body.replace('_', '((i in this) && fn.call(scope,this[i],i,this))') + '}' + ret;
		/* jshint ignore:start */
		return Function('fn,scope', fun);
		/* jshint ignore:end */
	}
	/* istanbul ignore if*/
	if (!isNative(ap.map)) {
		avalon.shadowCopy(ap, {
			//定位操作，返回数组中第一个等于给定参数的元素的索引值。
			indexOf: function indexOf(item, index) {
				var n = this.length,
					i = ~~index;
				if (i < 0) i += n;
				for (; i < n; i++) {
					if (this[i] === item) return i;
				}return -1;
			},
			//定位操作，同上，不过是从后遍历。
			lastIndexOf: function lastIndexOf(item, index) {
				var n = this.length,
					i = index == null ? n - 1 : index;
				if (i < 0) i = Math.max(0, n + i);
				for (; i >= 0; i--) {
					if (this[i] === item) return i;
				}return -1;
			},
			//迭代操作，将数组的元素挨个儿传入一个函数中执行。Prototype.js的对应名字为each。
			forEach: iterator('', '_', ''),
			//迭代类 在数组中的每个项上运行一个函数，如果此函数的值为真，则此元素作为新数组的元素收集起来，并返回新数组
			filter: iterator('r=[],j=0,', 'if(_)r[j++]=this[i]', 'return r'),
			//收集操作，将数组的元素挨个儿传入一个函数中执行，然后把它们的返回值组成一个新数组返回。Prototype.js的对应名字为collect。
			map: iterator('r=[],', 'r[i]=_', 'return r'),
			//只要数组中有一个元素满足条件（放进给定函数返回true），那么它就返回true。Prototype.js的对应名字为any。
			some: iterator('', 'if(_)return true', 'return false'),
			//只有数组中的元素都满足条件（放进给定函数返回true），它才返回true。Prototype.js的对应名字为all。
			every: iterator('', 'if(!_)return false', 'return true')
		});
	}

	//这里放置存在异议的方法
	var compaceQuote = function () {
		//https://github.com/bestiejs/json3/blob/master/lib/json3.js
		var Escapes = {
			92: "\\\\",
			34: '\\"',
			8: "\\b",
			12: "\\f",
			10: "\\n",
			13: "\\r",
			9: "\\t"
		};

		var leadingZeroes = '000000';
		var toPaddedString = function toPaddedString(width, value) {
			return (leadingZeroes + (value || 0)).slice(-width);
		};
		var unicodePrefix = '\\u00';
		var escapeChar = function escapeChar(character) {
			var charCode = character.charCodeAt(0),
				escaped = Escapes[charCode];
			if (escaped) {
				return escaped;
			}
			return unicodePrefix + toPaddedString(2, charCode.toString(16));
		};
		var reEscape = /[\x00-\x1f\x22\x5c]/g;
		return function (value) {
			/* istanbul ignore next */
			reEscape.lastIndex = 0;
			/* istanbul ignore next */
			return '"' + (reEscape.test(value) ? String(value).replace(reEscape, escapeChar) : value) + '"';
		};
	}();
	try {
		avalon._quote = msie <= 8 ? compaceQuote : JSON.stringify;
	} catch (e) {
		/* istanbul ignore next  */
		avalon._quote = compaceQuote;
	}

	var class2type = {};
	'Boolean Number String Function Array Date RegExp Object Error'.replace(avalon.rword, function (name) {
		class2type['[object ' + name + ']'] = name.toLowerCase();
	});

	avalon.type = function (obj) {
		//取得目标的类型
		if (obj == null) {
			return String(obj);
		}
		// 早期的webkit内核浏览器实现了已废弃的ecma262v4标准，可以将正则字面量当作函数使用，因此typeof在判定正则时会返回function
		return typeof obj === 'object' || typeof obj === 'function' ? class2type[inspect.call(obj)] || 'object' : typeof obj;
	};

	var rfunction = /^\s*\bfunction\b/;

	avalon.isFunction = /* istanbul ignore if */typeof alert === 'object' ? function (fn) {
		/* istanbul ignore next */
		try {
			/* istanbul ignore next */
			return rfunction.test(fn + '');
		} catch (e) {
			/* istanbul ignore next */
			return false;
		}
	} : function (fn) {
		return inspect.call(fn) === '[object Function]';
	};

	// 利用IE678 window == document为true,document == window竟然为false的神奇特性
	// 标准浏览器及IE9，IE10等使用 正则检测
	/* istanbul ignore next */
	function isWindowCompact(obj) {
		if (!obj) {
			return false;
		}
		return obj == obj.document && obj.document != obj; //jshint ignore:line
	}

	var rwindow = /^\[object (?:Window|DOMWindow|global)\]$/;

	function isWindowModern(obj) {
		return rwindow.test(inspect.call(obj));
	}

	avalon.isWindow = isWindowModern(avalon.window) ? isWindowModern : isWindowCompact;

	var enu;
	var enumerateBUG;
	for (enu in avalon({})) {
		break;
	}

	enumerateBUG = enu !== '0'; //IE6下为true, 其他为false

	/*判定是否是一个朴素的javascript对象（Object），不是DOM对象，不是BOM对象，不是自定义类的实例*/
	/* istanbul ignore next */
	function isPlainObjectCompact(obj, key) {
		if (!obj || avalon.type(obj) !== 'object' || obj.nodeType || avalon.isWindow(obj)) {
			return false;
		}
		try {
			//IE内置对象没有constructor
			if (obj.constructor && !ohasOwn.call(obj, 'constructor') && !ohasOwn.call(obj.constructor.prototype, 'isPrototypeOf')) {
				return false;
			}
			var isVBscript = obj.$vbthis;
		} catch (e) {
			//IE8 9会在这里抛错
			return false;
		}
		/* istanbul ignore if */
		if (enumerateBUG) {
			for (key in obj) {
				return ohasOwn.call(obj, key);
			}
		}
		for (key in obj) {}
		return key === undefined$1 || ohasOwn.call(obj, key);
	}

	/* istanbul ignore next */
	function isPlainObjectModern(obj) {
		// 简单的 typeof obj === 'object'检测，会致使用isPlainObject(window)在opera下通不过
		return inspect.call(obj) === '[object Object]' && Object.getPrototypeOf(obj) === Object.prototype;
	}
	/* istanbul ignore next */
	avalon.isPlainObject = /\[native code\]/.test(Object.getPrototypeOf) ? isPlainObjectModern : isPlainObjectCompact;

	var rcanMix = /object|function/;

	//与jQuery.extend方法，可用于浅拷贝，深拷贝
	/* istanbul ignore next */
	avalon.mix = avalon.fn.mix = function () {
		var n = arguments.length,
			isDeep = false,
			i = 0,
			array = [];
		if (arguments[0] === true) {
			isDeep = true;
			i = 1;
		}
		//将所有非空对象变成空对象
		for (; i < n; i++) {
			var el = arguments[i];
			el = el && rcanMix.test(typeof el) ? el : {};
			array.push(el);
		}
		if (array.length === 1) {
			array.unshift(this);
		}
		return innerExtend(isDeep, array);
	};
	var undefined$1;

	function innerExtend(isDeep, array) {
		var target = array[0],
			copyIsArray,
			clone,
			name;
		for (var i = 1, length = array.length; i < length; i++) {
			//只处理非空参数
			var options = array[i];
			var noCloneArrayMethod = Array.isArray(options);
			for (name in options) {
				if (noCloneArrayMethod && !options.hasOwnProperty(name)) {
					continue;
				}
				try {
					var src = target[name];
					var copy = options[name]; //当options为VBS对象时报错
				} catch (e) {
					continue;
				}

				// 防止环引用
				if (target === copy) {
					continue;
				}
				if (isDeep && copy && (avalon.isPlainObject(copy) || (copyIsArray = Array.isArray(copy)))) {

					if (copyIsArray) {
						copyIsArray = false;
						clone = src && Array.isArray(src) ? src : [];
					} else {
						clone = src && avalon.isPlainObject(src) ? src : {};
					}

					target[name] = innerExtend(isDeep, [clone, copy]);
				} else if (copy !== undefined$1) {
					target[name] = copy;
				}
			}
		}
		return target;
	}

	var rarraylike = /(Array|List|Collection|Map|Arguments)\]$/;
	/*判定是否类数组，如节点集合，纯数组，arguments与拥有非负整数的length属性的纯JS对象*/
	/* istanbul ignore next */
	function isArrayLike(obj) {
		if (!obj) return false;
		var n = obj.length;
		if (n === n >>> 0) {
			//检测length属性是否为非负整数
			var type = inspect.call(obj);
			if (rarraylike.test(type)) return true;
			if (type !== '[object Object]') return false;
			try {
				if ({}.propertyIsEnumerable.call(obj, 'length') === false) {
					//如果是原生对象
					return rfunction.test(obj.item || obj.callee);
				}
				return true;
			} catch (e) {
				//IE的NodeList直接抛错
				return !obj.window; //IE6-8 window
			}
		}
		return false;
	}

	avalon.each = function (obj, fn) {
		if (obj) {
			//排除null, undefined
			var i = 0;
			if (isArrayLike(obj)) {
				for (var n = obj.length; i < n; i++) {
					if (fn(i, obj[i]) === false) break;
				}
			} else {
				for (i in obj) {
					if (obj.hasOwnProperty(i) && fn(i, obj[i]) === false) {
						break;
					}
				}
			}
		}
	};
	(function () {
		var welcomeIntro = ["%cavalon.js %c" + avalon.version + " %cin debug mode, %cmore...", "color: rgb(114, 157, 52); font-weight: normal;", "color: rgb(85, 85, 85); font-weight: normal;", "color: rgb(85, 85, 85); font-weight: normal;", "color: rgb(82, 140, 224); font-weight: normal; text-decoration: underline;"];
		var welcomeMessage = "You're running avalon in debug mode - messages will be printed to the console to help you fix problems and optimise your application.\n\n" + 'To disable debug mode, add this line at the start of your app:\n\n  avalon.config({debug: false});\n\n' + 'Debug mode also automatically shut down amicably when your app is minified.\n\n' + "Get help and support:\n  https://segmentfault.com/t/avalon\n  http://avalonjs.coding.me/\n  http://www.baidu-x.com/?q=avalonjs\n  http://www.avalon.org.cn/\n\nFound a bug? Raise an issue:\n  https://github.com/RubyLouvre/avalon/issues\n\n";
		if (typeof console === 'object') {
			var con = console;
			var method = con.groupCollapsed || con.log;
			Function.apply.call(method, con, welcomeIntro);
			con.log(welcomeMessage);
			if (method !== console.log) {
				con.groupEnd(welcomeIntro);
			}
		}
	})();

	function toFixedFix(n, prec) {
		var k = Math.pow(10, prec);
		return '' + (Math.round(n * k) / k).toFixed(prec);
	}
	function numberFilter(number, decimals, point, thousands) {
		//https://github.com/txgruppi/number_format
		//form http://phpjs.org/functions/number_format/
		//number 必需，要格式化的数字
		//decimals 可选，规定多少个小数位。
		//point 可选，规定用作小数点的字符串（默认为 . ）。
		//thousands 可选，规定用作千位分隔符的字符串（默认为 , ），如果设置了该参数，那么所有其他参数都是必需的。
		number = (number + '').replace(/[^0-9+\-Ee.]/g, '');
		var n = !isFinite(+number) ? 0 : +number,
			prec = !isFinite(+decimals) ? 3 : Math.abs(decimals),
			sep = typeof thousands === 'string' ? thousands : ",",
			dec = point || ".",
			s = '';

		// Fix for IE parseFloat(0.55).toFixed(0) = 0;
		s = (prec ? toFixedFix(n, prec) : '' + Math.round(n)).split('.');
		if (s[0].length > 3) {
			s[0] = s[0].replace(/\B(?=(?:\d{3})+(?!\d))/g, sep);
		}
		/** //好像没有用
		 var s1 = s[1] || ''

		 if (s1.length < prec) {
                  s1 += new Array(prec - s[1].length + 1).join('0')
                  s[1] = s1
          }
		 **/
		return s.join(dec);
	}

	var rscripts = /<script[^>]*>([\S\s]*?)<\/script\s*>/gim;
	var ron = /\s+(on[^=\s]+)(?:=("[^"]*"|'[^']*'|[^\s>]+))?/g;
	var ropen = /<\w+\b(?:(["'])[^"]*?(\1)|[^>])*>/ig;
	var rsanitize = {
		a: /\b(href)\=("javascript[^"]*"|'javascript[^']*')/ig,
		img: /\b(src)\=("javascript[^"]*"|'javascript[^']*')/ig,
		form: /\b(action)\=("javascript[^"]*"|'javascript[^']*')/ig
	};

	//https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet
	//    <a href="javasc&NewLine;ript&colon;alert('XSS')">chrome</a>
	//    <a href="data:text/html;base64, PGltZyBzcmM9eCBvbmVycm9yPWFsZXJ0KDEpPg==">chrome</a>
	//    <a href="jav	ascript:alert('XSS');">IE67chrome</a>
	//    <a href="jav&#x09;ascript:alert('XSS');">IE67chrome</a>
	//    <a href="jav&#x0A;ascript:alert('XSS');">IE67chrome</a>
	function sanitizeFilter(str) {
		return str.replace(rscripts, "").replace(ropen, function (a, b) {
			var match = a.toLowerCase().match(/<(\w+)\s/);
			if (match) {
				//处理a标签的href属性，img标签的src属性，form标签的action属性
				var reg = rsanitize[match[1]];
				if (reg) {
					a = a.replace(reg, function (s, name, value) {
						var quote = value.charAt(0);
						return name + "=" + quote + "javascript:void(0)" + quote; // jshint ignore:line
					});
				}
			}
			return a.replace(ron, " ").replace(/\s+/g, " "); //移除onXXX事件
		});
	}

	/*
     'yyyy': 4 digit representation of year (e.g. AD 1 => 0001, AD 2010 => 2010)
     'yy': 2 digit representation of year, padded (00-99). (e.g. AD 2001 => 01, AD 2010 => 10)
     'y': 1 digit representation of year, e.g. (AD 1 => 1, AD 199 => 199)
     'MMMM': Month in year (January-December)
     'MMM': Month in year (Jan-Dec)
     'MM': Month in year, padded (01-12)
     'M': Month in year (1-12)
     'dd': Day in month, padded (01-31)
     'd': Day in month (1-31)
     'EEEE': Day in Week,(Sunday-Saturday)
     'EEE': Day in Week, (Sun-Sat)
     'HH': Hour in day, padded (00-23)
     'H': Hour in day (0-23)
     'hh': Hour in am/pm, padded (01-12)
     'h': Hour in am/pm, (1-12)
     'mm': Minute in hour, padded (00-59)
     'm': Minute in hour (0-59)
     'ss': Second in minute, padded (00-59)
     's': Second in minute (0-59)
     'a': am/pm marker
     'Z': 4 digit (+sign) representation of the timezone offset (-1200-+1200)
     format string can also be one of the following predefined localizable formats:

     'medium': equivalent to 'MMM d, y h:mm:ss a' for en_US locale (e.g. Sep 3, 2010 12:05:08 pm)
     'short': equivalent to 'M/d/yy h:mm a' for en_US locale (e.g. 9/3/10 12:05 pm)
     'fullDate': equivalent to 'EEEE, MMMM d,y' for en_US locale (e.g. Friday, September 3, 2010)
     'longDate': equivalent to 'MMMM d, y' for en_US locale (e.g. September 3, 2010
     'mediumDate': equivalent to 'MMM d, y' for en_US locale (e.g. Sep 3, 2010)
     'shortDate': equivalent to 'M/d/yy' for en_US locale (e.g. 9/3/10)
     'mediumTime': equivalent to 'h:mm:ss a' for en_US locale (e.g. 12:05:08 pm)
     'shortTime': equivalent to 'h:mm a' for en_US locale (e.g. 12:05 pm)
     */

	function toInt(str) {
		return parseInt(str, 10) || 0;
	}

	function padNumber(num, digits, trim) {
		var neg = '';
		/* istanbul ignore if*/
		if (num < 0) {
			neg = '-';
			num = -num;
		}
		num = '' + num;
		while (num.length < digits) {
			num = '0' + num;
		}if (trim) num = num.substr(num.length - digits);
		return neg + num;
	}

	function dateGetter(name, size, offset, trim) {
		return function (date) {
			var value = date["get" + name]();
			if (offset > 0 || value > -offset) value += offset;
			if (value === 0 && offset === -12) {
				/* istanbul ignore next*/
				value = 12;
			}
			return padNumber(value, size, trim);
		};
	}

	function dateStrGetter(name, shortForm) {
		return function (date, formats) {
			var value = date["get" + name]();
			var get = (shortForm ? "SHORT" + name : name).toUpperCase();
			return formats[get][value];
		};
	}

	function timeZoneGetter(date) {
		var zone = -1 * date.getTimezoneOffset();
		var paddedZone = zone >= 0 ? "+" : "";
		paddedZone += padNumber(Math[zone > 0 ? "floor" : "ceil"](zone / 60), 2) + padNumber(Math.abs(zone % 60), 2);
		return paddedZone;
	}
	//取得上午下午
	function ampmGetter(date, formats) {
		return date.getHours() < 12 ? formats.AMPMS[0] : formats.AMPMS[1];
	}
	var DATE_FORMATS = {
		yyyy: dateGetter("FullYear", 4),
		yy: dateGetter("FullYear", 2, 0, true),
		y: dateGetter("FullYear", 1),
		MMMM: dateStrGetter("Month"),
		MMM: dateStrGetter("Month", true),
		MM: dateGetter("Month", 2, 1),
		M: dateGetter("Month", 1, 1),
		dd: dateGetter("Date", 2),
		d: dateGetter("Date", 1),
		HH: dateGetter("Hours", 2),
		H: dateGetter("Hours", 1),
		hh: dateGetter("Hours", 2, -12),
		h: dateGetter("Hours", 1, -12),
		mm: dateGetter("Minutes", 2),
		m: dateGetter("Minutes", 1),
		ss: dateGetter("Seconds", 2),
		s: dateGetter("Seconds", 1),
		sss: dateGetter("Milliseconds", 3),
		EEEE: dateStrGetter("Day"),
		EEE: dateStrGetter("Day", true),
		a: ampmGetter,
		Z: timeZoneGetter
	};
	var rdateFormat = /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z))(.*)/;
	var raspnetjson = /^\/Date\((\d+)\)\/$/;
	function dateFilter(date, format) {
		var locate = dateFilter.locate,
			text = "",
			parts = [],
			fn,
			match;
		format = format || "mediumDate";
		format = locate[format] || format;
		if (typeof date === "string") {
			if (/^\d+$/.test(date)) {
				date = toInt(date);
			} else if (raspnetjson.test(date)) {
				date = +RegExp.$1;
			} else {
				var trimDate = date.trim();
				var dateArray = [0, 0, 0, 0, 0, 0, 0];
				var oDate = new Date(0);
				//取得年月日
				trimDate = trimDate.replace(/^(\d+)\D(\d+)\D(\d+)/, function (_, a, b, c) {
					var array = c.length === 4 ? [c, a, b] : [a, b, c];
					dateArray[0] = toInt(array[0]); //年
					dateArray[1] = toInt(array[1]) - 1; //月
					dateArray[2] = toInt(array[2]); //日
					return "";
				});
				var dateSetter = oDate.setFullYear;
				var timeSetter = oDate.setHours;
				trimDate = trimDate.replace(/[T\s](\d+):(\d+):?(\d+)?\.?(\d)?/, function (_, a, b, c, d) {
					dateArray[3] = toInt(a); //小时
					dateArray[4] = toInt(b); //分钟
					dateArray[5] = toInt(c); //秒
					if (d) {
						//毫秒
						dateArray[6] = Math.round(parseFloat("0." + d) * 1000);
					}
					return "";
				});
				var tzHour = 0;
				var tzMin = 0;
				trimDate = trimDate.replace(/Z|([+-])(\d\d):?(\d\d)/, function (z, symbol, c, d) {
					dateSetter = oDate.setUTCFullYear;
					timeSetter = oDate.setUTCHours;
					if (symbol) {
						tzHour = toInt(symbol + c);
						tzMin = toInt(symbol + d);
					}
					return '';
				});

				dateArray[3] -= tzHour;
				dateArray[4] -= tzMin;
				dateSetter.apply(oDate, dateArray.slice(0, 3));
				timeSetter.apply(oDate, dateArray.slice(3));
				date = oDate;
			}
		}
		if (typeof date === 'number') {
			date = new Date(date);
		}

		while (format) {
			match = rdateFormat.exec(format);
			/* istanbul ignore else */
			if (match) {
				parts = parts.concat(match.slice(1));
				format = parts.pop();
			} else {
				parts.push(format);
				format = null;
			}
		}
		parts.forEach(function (value) {
			fn = DATE_FORMATS[value];
			text += fn ? fn(date, locate) : value.replace(/(^'|'$)/g, "").replace(/''/g, "'");
		});
		return text;
	}

	var locate = {
		AMPMS: {
			0: '上午',
			1: '下午'
		},
		DAY: {
			0: '星期日',
			1: '星期一',
			2: '星期二',
			3: '星期三',
			4: '星期四',
			5: '星期五',
			6: '星期六'
		},
		MONTH: {
			0: '1月',
			1: '2月',
			2: '3月',
			3: '4月',
			4: '5月',
			5: '6月',
			6: '7月',
			7: '8月',
			8: '9月',
			9: '10月',
			10: '11月',
			11: '12月'
		},
		SHORTDAY: {
			'0': '周日',
			'1': '周一',
			'2': '周二',
			'3': '周三',
			'4': '周四',
			'5': '周五',
			'6': '周六'
		},
		fullDate: 'y年M月d日EEEE',
		longDate: 'y年M月d日',
		medium: 'yyyy-M-d H:mm:ss',
		mediumDate: 'yyyy-M-d',
		mediumTime: 'H:mm:ss',
		'short': 'yy-M-d ah:mm',
		shortDate: 'yy-M-d',
		shortTime: 'ah:mm'
	};
	locate.SHORTMONTH = locate.MONTH;
	dateFilter.locate = locate;

	/**
	 $$skipArray:是系统级通用的不可监听属性
	 $skipArray: 是当前对象特有的不可监听属性

	 不同点是
	 $$skipArray被hasOwnProperty后返回false
	 $skipArray被hasOwnProperty后返回true
	 */
	var falsy;
	var $$skipArray = {
		$id: falsy,
		$render: falsy,
		$track: falsy,
		$element: falsy,
		$computed: falsy,
		$watch: falsy,
		$fire: falsy,
		$events: falsy,
		$accessors: falsy,
		$hashcode: falsy,
		$mutations: falsy,
		$vbthis: falsy,
		$vbsetter: falsy
	};

	/*
    https://github.com/hufyhang/orderBy/blob/master/index.js
    */

	function orderBy(array, by, decend) {
		var type = avalon.type(array);
		if (type !== 'array' && type !== 'object') throw 'orderBy只能处理对象或数组';
		var criteria = typeof by == 'string' ? function (el) {
			return el && el[by];
		} : typeof by === 'function' ? by : function (el) {
			return el;
		};
		var mapping = {};
		var temp = [];
		__repeat(array, Array.isArray(array), function (key) {
			var val = array[key];
			var k = criteria(val, key);
			if (k in mapping) {
				mapping[k].push(key);
			} else {
				mapping[k] = [key];
			}
			temp.push(k);
		});

		temp.sort();
		if (decend < 0) {
			temp.reverse();
		}
		var _array = type === 'array';
		var target = _array ? [] : {};
		return recovery(target, temp, function (k) {
			var key = mapping[k].shift();
			if (_array) {
				target.push(array[key]);
			} else {
				target[key] = array[key];
			}
		});
	}

	function __repeat(array, isArray$$1, cb) {
		if (isArray$$1) {
			array.forEach(function (val, index) {
				cb(index);
			});
		} else if (typeof array.$track === 'string') {
			array.$track.replace(/[^☥]+/g, function (k) {
				cb(k);
			});
		} else {
			for (var i in array) {
				if (array.hasOwnProperty(i)) {
					cb(i);
				}
			}
		}
	}
	function filterBy(array, search) {
		var type = avalon.type(array);
		if (type !== 'array' && type !== 'object') throw 'filterBy只能处理对象或数组';
		var args = avalon.slice(arguments, 2);
		var stype = avalon.type(search);
		if (stype === 'function') {
			var criteria = search._orig || search;
		} else if (stype === 'string' || stype === 'number') {
			if (search === '') {
				return array;
			} else {
				var reg = new RegExp(avalon.escapeRegExp(search), 'i');
				criteria = function criteria(el) {
					return reg.test(el);
				};
			}
		} else {
			return array;
		}
		var isArray$$1 = type === 'array';
		var target = isArray$$1 ? [] : {};
		__repeat(array, isArray$$1, function (key) {
			var val = array[key];
			if (criteria.apply({
				key: key
			}, [val, key].concat(args))) {
				if (isArray$$1) {
					target.push(val);
				} else {
					target[key] = val;
				}
			}
		});
		return target;
	}

	function selectBy(data, array, defaults) {
		if (avalon.isObject(data) && !Array.isArray(data)) {
			var target = [];
			return recovery(target, array, function (name) {
				target.push(data.hasOwnProperty(name) ? data[name] : defaults ? defaults[name] : '');
			});
		} else {
			return data;
		}
	}

	function limitBy(input, limit, begin) {
		var type = avalon.type(input);
		if (type !== 'array' && type !== 'object') throw 'limitBy只能处理对象或数组';
		//必须是数值
		if (typeof limit !== 'number') {
			return input;
		}
		//不能为NaN
		if (limit !== limit) {
			return input;
		}
		//将目标转换为数组
		if (type === 'object') {
			input = convertArray(input, false);
		}
		var n = input.length;
		limit = Math.floor(Math.min(n, limit));
		begin = typeof begin === 'number' ? begin : 0;
		if (begin < 0) {
			begin = Math.max(0, n + begin);
		}
		var data = [];
		for (var i = begin; i < n; i++) {
			if (data.length === limit) {
				break;
			}
			data.push(input[i]);
		}
		var isArray$$1 = type === 'array';
		if (isArray$$1) {
			return data;
		}
		var target = {};
		return recovery(target, data, function (el) {
			target[el.key] = el.value;
		});
	}

	function recovery(ret, array, callback) {
		for (var i = 0, n = array.length; i < n; i++) {
			callback(array[i]);
		}
		return ret;
	}

	//Chrome谷歌浏览器中js代码Array.sort排序的bug乱序解决办法
	//http://www.cnblogs.com/yzeng/p/3949182.html
	function convertArray(array, isArray$$1) {
		var ret = [],
			i = 0;
		__repeat(array, isArray$$1, function (key) {
			ret[i] = {
				oldIndex: i,
				value: array[key],
				key: key
			};
			i++;
		});
		return ret;
	}

	var eventFilters = {
		stop: function stop(e) {
			e.stopPropagation();
			return e;
		},
		prevent: function prevent(e) {
			e.preventDefault();
			return e;
		}
	};
	var keys = {
		esc: 27,
		tab: 9,
		enter: 13,
		space: 32,
		del: 46,
		up: 38,
		left: 37,
		right: 39,
		down: 40
	};
	for (var name$1 in keys) {
		(function (filter, key) {
			eventFilters[filter] = function (e) {
				if (e.which !== key) {
					e.$return = true;
				}
				return e;
			};
		})(name$1, keys[name$1]);
	}

	//https://github.com/teppeis/htmlspecialchars
	function escapeFilter(str) {
		if (str == null) return '';

		return String(str).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#39;');
	}

	var filters = avalon.filters = {};

	avalon.composeFilters = function () {
		var args = arguments;
		return function (value) {
			for (var i = 0, arr; arr = args[i++];) {
				var name = arr[0];
				var filter = avalon.filters[name];
				if (typeof filter === 'function') {
					arr[0] = value;
					try {
						value = filter.apply(0, arr);
					} catch (e) {}
				}
			}
			return value;
		};
	};

	avalon.escapeHtml = escapeFilter;

	avalon.mix(filters, {
		uppercase: function uppercase(str) {
			return String(str).toUpperCase();
		},
		lowercase: function lowercase(str) {
			return String(str).toLowerCase();
		},
		truncate: function truncate(str, length, end) {
			//length，新字符串长度，truncation，新字符串的结尾的字段,返回新字符串
			if (!str) {
				return '';
			}
			str = String(str);
			if (isNaN(length)) {
				length = 30;
			}
			end = typeof end === "string" ? end : "...";
			return str.length > length ? str.slice(0, length - end.length) + end : /* istanbul ignore else*/
				str;
		},

		camelize: avalon.camelize,
		date: dateFilter,
		escape: escapeFilter,
		sanitize: sanitizeFilter,
		number: numberFilter,
		currency: function currency(amount, symbol, fractionSize) {
			return (symbol || '\xA5') + numberFilter(amount, isFinite(fractionSize) ? /* istanbul ignore else*/fractionSize : 2);
		}
	}, { filterBy: filterBy, orderBy: orderBy, selectBy: selectBy, limitBy: limitBy }, eventFilters);

	var rcheckedType = /^(?:checkbox|radio)$/;

	/* istanbul ignore next */
	function fixElement(dest, src) {
		if (dest.nodeType !== 1) {
			return;
		}
		var nodeName = dest.nodeName.toLowerCase();

		if (nodeName === "script") {
			if (dest.text !== src.text) {
				dest.type = "noexec";
				dest.text = src.text;
				dest.type = src.type || "";
			}
		} else if (nodeName === 'object') {
			var params = src.childNodes;
			if (dest.childNodes.length !== params.length) {
				avalon.clearHTML(dest);
				for (var i = 0, el; el = params[i++];) {
					dest.appendChild(el.cloneNode(true));
				}
			}
		} else if (nodeName === 'input' && rcheckedType.test(src.nodeName)) {

			dest.defaultChecked = dest.checked = src.checked;
			if (dest.value !== src.value) {
				dest.value = src.value;
			}
		} else if (nodeName === 'option') {
			dest.defaultSelected = dest.selected = src.defaultSelected;
		} else if (nodeName === 'input' || nodeName === 'textarea') {
			dest.defaultValue = src.defaultValue;
		}
	}

	/* istanbul ignore next */
	function getAll(context) {
		return typeof context.getElementsByTagName !== 'undefined' ? context.getElementsByTagName('*') : typeof context.querySelectorAll !== 'undefined' ? context.querySelectorAll('*') : [];
	}

	/* istanbul ignore next */
	function fixClone(src) {
		var target = src.cloneNode(true);
		//http://www.myexception.cn/web/665613.html
		// target.expando = null
		var t = getAll(target);
		var s = getAll(src);
		for (var i = 0; i < s.length; i++) {
			fixElement(t[i], s[i]);
		}
		return target;
	}

	/* istanbul ignore next */
	function fixContains(root, el) {
		try {
			//IE6-8,游离于DOM树外的文本节点，访问parentNode有时会抛错
			while (el = el.parentNode) {
				if (el === root) return true;
			}
		} catch (e) {}
		return false;
	}

	avalon.contains = fixContains;

	avalon.cloneNode = function (a) {
		return a.cloneNode(true);
	};

	//IE6-11的文档对象没有contains
	/* istanbul ignore next */
	function shimHack() {
		if (msie$1 < 10) {
			avalon.cloneNode = fixClone;
		}
		if (!document$1.contains) {
			document$1.contains = function (b) {
				return fixContains(document$1, b);
			};
		}
		if (avalon.modern) {
			if (!document$1.createTextNode('x').contains) {
				Node.prototype.contains = function (child) {
					//IE6-8没有Node对象
					return fixContains(this, child);
				};
			}
		}
		//firefox 到11时才有outerHTML
		function fixFF(prop, cb) {
			if (!(prop in root) && HTMLElement.prototype.__defineGetter__) {
				HTMLElement.prototype.__defineGetter__(prop, cb);
			}
		}
		fixFF('outerHTML', function () {
			var div = document$1.createElement('div');
			div.appendChild(this);
			return div.innerHTML;
		});
		fixFF('children', function () {
			var children = [];
			for (var i = 0, el; el = this.childNodes[i++];) {
				if (el.nodeType === 1) {
					children.push(el);
				}
			}
			return children;
		});
		fixFF('innerText', function () {
			//firefox45+, chrome4+ http://caniuse.com/#feat=innertext
			return this.textContent;
		});
	}

	if (inBrowser) {
		shimHack();
	}

	function ClassList(node) {
		this.node = node;
	}

	ClassList.prototype = {
		toString: function toString() {
			var node = this.node;
			var cls = node.className;
			var str = typeof cls === 'string' ? cls : cls.baseVal;
			var match = str.match(rnowhite);
			return match ? match.join(' ') : '';
		},
		contains: function contains(cls) {
			return (' ' + this + ' ').indexOf(' ' + cls + ' ') > -1;
		},
		add: function add(cls) {
			if (!this.contains(cls)) {
				this.set(this + ' ' + cls);
			}
		},
		remove: function remove(cls) {
			this.set((' ' + this + ' ').replace(' ' + cls + ' ', ' '));
		},
		set: function set(cls) {
			cls = cls.trim();
			var node = this.node;
			if (typeof node.className === 'object') {
				//SVG元素的className是一个对象 SVGAnimatedString { baseVal='', animVal=''}，只能通过set/getAttribute操作
				node.setAttribute('class', cls);
			} else {
				node.className = cls;
			}
			if (!cls) {
				node.removeAttribute('class');
			}
			//toggle存在版本差异，因此不使用它
		}
	};

	function classListFactory(node) {
		if (!('classList' in node)) {
			node.classList = new ClassList(node);
		}
		return node.classList;
	}

	'add,remove'.replace(rword, function (method) {
		avalon.fn[method + 'Class'] = function (cls) {
			var el = this[0] || {};
			//https://developer.mozilla.org/zh-CN/docs/Mozilla/Firefox/Releases/26
			if (cls && typeof cls === 'string' && el.nodeType === 1) {
				cls.replace(rnowhite, function (c) {
					classListFactory(el)[method](c);
				});
			}
			return this;
		};
	});

	avalon.shadowCopy(avalon.fn, {
		hasClass: function hasClass(cls) {
			var el = this[0] || {};
			return el.nodeType === 1 && classListFactory(el).contains(cls);
		},
		toggleClass: function toggleClass(value, stateVal) {
			var isBool = typeof stateVal === 'boolean';
			var me = this;
			String(value).replace(rnowhite, function (c) {
				var state = isBool ? stateVal : !me.hasClass(c);
				me[state ? 'addClass' : 'removeClass'](c);
			});
			return this;
		}
	});

	var propMap = {}; //不规则的属性名映射


	//防止压缩时出错
	'accept-charset,acceptCharset|char,ch|charoff,chOff|class,className|for,htmlFor|http-equiv,httpEquiv'.replace(/[^\|]+/g, function (a) {
		var k = a.split(',');
		propMap[k[0]] = k[1];
	});
	/*
    contenteditable不是布尔属性
    http://www.zhangxinxu.com/wordpress/2016/01/contenteditable-plaintext-only/
    contenteditable=''
    contenteditable='events'
    contenteditable='caret'
    contenteditable='plaintext-only'
    contenteditable='true'
    contenteditable='false'
     */
	var bools = ['autofocus,autoplay,async,allowTransparency,checked,controls', 'declare,disabled,defer,defaultChecked,defaultSelected,', 'isMap,loop,multiple,noHref,noResize,noShade', 'open,readOnly,selected'].join(',');

	bools.replace(/\w+/g, function (name) {
		propMap[name.toLowerCase()] = name;
	});

	var anomaly = ['accessKey,bgColor,cellPadding,cellSpacing,codeBase,codeType,colSpan', 'dateTime,defaultValue,contentEditable,frameBorder,longDesc,maxLength,' + 'marginWidth,marginHeight,rowSpan,tabIndex,useMap,vSpace,valueType,vAlign'].join(',');

	anomaly.replace(/\w+/g, function (name) {
		propMap[name.toLowerCase()] = name;
	});

	//module.exports = propMap

	function isVML(src) {
		var nodeName = src.nodeName;
		return nodeName.toLowerCase() === nodeName && !!src.scopeName && src.outerText === '';
	}

	var rvalidchars = /^[\],:{}\s]*$/;
	var rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g;
	var rvalidescape = /\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g;
	var rvalidtokens = /"[^"\\\r\n]*"|true|false|null|-?(?:\d+\.|)\d+(?:[eE][+-]?\d+|)/g;

	function compactParseJSON(data) {
		if (typeof data === 'string') {
			data = data.trim();
			if (data) {
				if (rvalidchars.test(data.replace(rvalidescape, '@').replace(rvalidtokens, ']').replace(rvalidbraces, ''))) {
					return new Function('return ' + data)(); // jshint ignore:line
				}
			}
			throw TypeError('Invalid JSON: [' + data + ']');
		}
		return data;
	}

	var rsvg = /^\[object SVG\w*Element\]$/;
	var ramp = /&amp;/g;
	function updateAttrs(node, attrs) {
		for (var attrName in attrs) {
			try {
				var val = attrs[attrName];
				// 处理路径属性
				/* istanbul ignore if*/

				//处理HTML5 data-*属性 SVG
				if (attrName.indexOf('data-') === 0 || rsvg.test(node)) {
					node.setAttribute(attrName, val);
				} else {
					var propName = propMap[attrName] || attrName;
					/* istanbul ignore if */
					if (typeof node[propName] === 'boolean') {
						if (propName === 'checked') {
							node.defaultChecked = !!val;
						}
						node[propName] = !!val;
						//布尔属性必须使用el.xxx = true|false方式设值
						//如果为false, IE全系列下相当于setAttribute(xxx,''),
						//会影响到样式,需要进一步处理
					}

					if (val === false) {
						//移除属性
						node.removeAttribute(propName);
						continue;
					}
					//IE6中classNamme, htmlFor等无法检测它们为内建属性　
					if (avalon.msie < 8 && /[A-Z]/.test(propName)) {
						node[propName] = val + '';
						continue;
					}
					//SVG只能使用setAttribute(xxx, yyy), VML只能使用node.xxx = yyy ,
					//HTML的固有属性必须node.xxx = yyy
					/* istanbul ignore next */
					var isInnate = !avalon.modern && isVML(node) ? true : isInnateProps(node.nodeName, attrName);
					if (isInnate) {
						if (attrName === 'href' || attrName === 'src') {
							/* istanbul ignore if */
							if (avalon.msie < 8) {
								val = String(val).replace(ramp, '&'); //处理IE67自动转义的问题
							}
						}
						node[propName] = val + '';
					} else {
						node.setAttribute(attrName, val);
					}
				}
			} catch (e) {
				// 对象不支持此属性或方法 src https://github.com/ecomfe/zrender
				// 未知名称。\/n
				// e.message大概这样,需要trim
				//IE6-8,元素节点不支持其他元素节点的内置属性,如src, href, for
				/* istanbul ignore next */
				avalon.log(String(e.message).trim(), attrName, val);
			}
		}
	}
	var innateMap = {};

	function isInnateProps(nodeName, attrName) {
		var key = nodeName + ":" + attrName;
		if (key in innateMap) {
			return innateMap[key];
		}
		return innateMap[key] = attrName in document$1.createElement(nodeName);
	}
	try {
		avalon.parseJSON = JSON.parse;
	} catch (e) {
		/* istanbul ignore next */
		avalon.parseJSON = compactParseJSON;
	}

	avalon.fn.attr = function (name, value) {
		if (arguments.length === 2) {
			this[0].setAttribute(name, value);
			return this;
		} else {
			return this[0].getAttribute(name);
		}
	};

	var cssMap = oneObject('float', 'cssFloat');
	avalon.cssNumber = oneObject('animationIterationCount,columnCount,order,flex,flexGrow,flexShrink,fillOpacity,fontWeight,lineHeight,opacity,orphans,widows,zIndex,zoom');
	var prefixes = ['', '-webkit-', '-o-', '-moz-', '-ms-'];
	/* istanbul ignore next */
	avalon.cssName = function (name, host, camelCase) {
		if (cssMap[name]) {
			return cssMap[name];
		}
		host = host || avalon.root.style || {};
		for (var i = 0, n = prefixes.length; i < n; i++) {
			camelCase = avalon.camelize(prefixes[i] + name);
			if (camelCase in host) {
				return cssMap[name] = camelCase;
			}
		}
		return null;
	};
	/* istanbul ignore next */
	avalon.css = function (node, name, value, fn) {
		//读写删除元素节点的样式
		if (node instanceof avalon) {
			node = node[0];
		}
		if (node.nodeType !== 1) {
			return;
		}
		var prop = avalon.camelize(name);
		name = avalon.cssName(prop) || /* istanbul ignore next*/prop;
		if (value === void 0 || typeof value === 'boolean') {
			//获取样式
			fn = cssHooks[prop + ':get'] || cssHooks['@:get'];
			if (name === 'background') {
				name = 'backgroundColor';
			}
			var val = fn(node, name);
			return value === true ? parseFloat(val) || 0 : val;
		} else if (value === '') {
			//请除样式
			node.style[name] = '';
		} else {
			//设置样式
			if (value == null || value !== value) {
				return;
			}
			if (isFinite(value) && !avalon.cssNumber[prop]) {
				value += 'px';
			}
			fn = cssHooks[prop + ':set'] || cssHooks['@:set'];
			fn(node, name, value);
		}
	};
	/* istanbul ignore next */
	avalon.fn.css = function (name, value) {
		if (avalon.isPlainObject(name)) {
			for (var i in name) {
				avalon.css(this, i, name[i]);
			}
		} else {
			var ret = avalon.css(this, name, value);
		}
		return ret !== void 0 ? ret : this;
	};
	/* istanbul ignore next */
	avalon.fn.position = function () {
		var offsetParent,
			offset,
			elem = this[0],
			parentOffset = {
				top: 0,
				left: 0
			};
		if (!elem) {
			return parentOffset;
		}
		if (this.css('position') === 'fixed') {
			offset = elem.getBoundingClientRect();
		} else {
			offsetParent = this.offsetParent(); //得到真正的offsetParent
			offset = this.offset(); // 得到正确的offsetParent
			if (offsetParent[0].tagName !== 'HTML') {
				parentOffset = offsetParent.offset();
			}
			parentOffset.top += avalon.css(offsetParent[0], 'borderTopWidth', true);
			parentOffset.left += avalon.css(offsetParent[0], 'borderLeftWidth', true);

			// Subtract offsetParent scroll positions
			parentOffset.top -= offsetParent.scrollTop();
			parentOffset.left -= offsetParent.scrollLeft();
		}
		return {
			top: offset.top - parentOffset.top - avalon.css(elem, 'marginTop', true),
			left: offset.left - parentOffset.left - avalon.css(elem, 'marginLeft', true)
		};
	};
	/* istanbul ignore next */
	avalon.fn.offsetParent = function () {
		var offsetParent = this[0].offsetParent;
		while (offsetParent && avalon.css(offsetParent, 'position') === 'static') {
			offsetParent = offsetParent.offsetParent;
		}
		return avalon(offsetParent || avalon.root);
	};

	/* istanbul ignore next */
	cssHooks['@:set'] = function (node, name, value) {
		try {
			//node.style.width = NaN;node.style.width = 'xxxxxxx';
			//node.style.width = undefine 在旧式IE下会抛异常
			node.style[name] = value;
		} catch (e) {}
	};
	/* istanbul ignore next */
	cssHooks['@:get'] = function (node, name) {
		if (!node || !node.style) {
			throw new Error('getComputedStyle要求传入一个节点 ' + node);
		}
		var ret,
			styles = window$1.getComputedStyle(node, null);
		if (styles) {
			ret = name === 'filter' ? styles.getPropertyValue(name) : styles[name];
			if (ret === '') {
				ret = node.style[name]; //其他浏览器需要我们手动取内联样式
			}
		}
		return ret;
	};

	cssHooks['opacity:get'] = function (node) {
		var ret = cssHooks['@:get'](node, 'opacity');
		return ret === '' ? '1' : ret;
	};

	'top,left'.replace(avalon.rword, function (name) {
		cssHooks[name + ':get'] = function (node) {
			var computed = cssHooks['@:get'](node, name);
			return (/px$/.test(computed) ? computed : avalon(node).position()[name] + 'px'
			);
		};
	});

	var cssShow = {
		position: 'absolute',
		visibility: 'hidden',
		display: 'block'
	};

	var rdisplayswap = /^(none|table(?!-c[ea]).+)/;
	/* istanbul ignore next */
	function showHidden(node, array) {
		//http://www.cnblogs.com/rubylouvre/archive/2012/10/27/2742529.html
		if (node.offsetWidth <= 0) {
			//opera.offsetWidth可能小于0
			if (rdisplayswap.test(cssHooks['@:get'](node, 'display'))) {
				var obj = {
					node: node
				};
				for (var name in cssShow) {
					obj[name] = node.style[name];
					node.style[name] = cssShow[name];
				}
				array.push(obj);
			}
			var parent = node.parentNode;
			if (parent && parent.nodeType === 1) {
				showHidden(parent, array);
			}
		}
	}
	/* istanbul ignore next*/
	avalon.each({
		Width: 'width',
		Height: 'height'
	}, function (name, method) {
		var clientProp = 'client' + name,
			scrollProp = 'scroll' + name,
			offsetProp = 'offset' + name;
		cssHooks[method + ':get'] = function (node, which, override) {
			var boxSizing = -4;
			if (typeof override === 'number') {
				boxSizing = override;
			}
			which = name === 'Width' ? ['Left', 'Right'] : ['Top', 'Bottom'];
			var ret = node[offsetProp]; // border-box 0
			if (boxSizing === 2) {
				// margin-box 2
				return ret + avalon.css(node, 'margin' + which[0], true) + avalon.css(node, 'margin' + which[1], true);
			}
			if (boxSizing < 0) {
				// padding-box  -2
				ret = ret - avalon.css(node, 'border' + which[0] + 'Width', true) - avalon.css(node, 'border' + which[1] + 'Width', true);
			}
			if (boxSizing === -4) {
				// content-box -4
				ret = ret - avalon.css(node, 'padding' + which[0], true) - avalon.css(node, 'padding' + which[1], true);
			}
			return ret;
		};
		cssHooks[method + '&get'] = function (node) {
			var hidden = [];
			showHidden(node, hidden);
			var val = cssHooks[method + ':get'](node);
			for (var i = 0, obj; obj = hidden[i++];) {
				node = obj.node;
				for (var n in obj) {
					if (typeof obj[n] === 'string') {
						node.style[n] = obj[n];
					}
				}
			}
			return val;
		};
		avalon.fn[method] = function (value) {
			//会忽视其display
			var node = this[0];
			if (arguments.length === 0) {
				if (node.setTimeout) {
					//取得窗口尺寸
					return node['inner' + name] || node.document.documentElement[clientProp] || node.document.body[clientProp]; //IE6下前两个分别为undefined,0
				}
				if (node.nodeType === 9) {
					//取得页面尺寸
					var doc = node.documentElement;
					//FF chrome    html.scrollHeight< body.scrollHeight
					//IE 标准模式 : html.scrollHeight> body.scrollHeight
					//IE 怪异模式 : html.scrollHeight 最大等于可视窗口多一点？
					return Math.max(node.body[scrollProp], doc[scrollProp], node.body[offsetProp], doc[offsetProp], doc[clientProp]);
				}
				return cssHooks[method + '&get'](node);
			} else {
				return this.css(method, value);
			}
		};
		avalon.fn['inner' + name] = function () {
			return cssHooks[method + ':get'](this[0], void 0, -2);
		};
		avalon.fn['outer' + name] = function (includeMargin) {
			return cssHooks[method + ':get'](this[0], void 0, includeMargin === true ? 2 : 0);
		};
	});

	function getWindow(node) {
		return node.window || node.defaultView || node.parentWindow || false;
	}

	/* istanbul ignore if */
	if (msie$1 < 9) {
		avalon.shadowCopy(cssMap, oneObject('float', 'styleFloat'));
		var rnumnonpx = /^-?(?:\d*\.)?\d+(?!px)[^\d\s]+$/i;
		var rposition = /^(top|right|bottom|left)$/;
		var ralpha = /alpha\([^)]+\)/i;
		var ropactiy = /(opacity|\d(\d|\.)*)/g;
		var ie8 = msie$1 === 8;
		var salpha = 'DXImageTransform.Microsoft.Alpha';
		var border = {
			thin: ie8 ? '1px' : '2px',
			medium: ie8 ? '3px' : '4px',
			thick: ie8 ? '5px' : '6px'
		};
		cssHooks['@:get'] = function (node, name) {
			//取得精确值，不过它有可能是带em,pc,mm,pt,%等单位
			var currentStyle = node.currentStyle;
			var ret = currentStyle[name];
			if (rnumnonpx.test(ret) && !rposition.test(ret)) {
				//①，保存原有的style.left, runtimeStyle.left,
				var style = node.style,
					left = style.left,
					rsLeft = node.runtimeStyle.left;
				//②由于③处的style.left = xxx会影响到currentStyle.left，
				//因此把它currentStyle.left放到runtimeStyle.left，
				//runtimeStyle.left拥有最高优先级，不会style.left影响
				node.runtimeStyle.left = currentStyle.left;
				//③将精确值赋给到style.left，然后通过IE的另一个私有属性 style.pixelLeft
				//得到单位为px的结果；fontSize的分支见http://bugs.jquery.com/ticket/760
				style.left = name === 'fontSize' ? '1em' : ret || 0;
				ret = style.pixelLeft + 'px';
				//④还原 style.left，runtimeStyle.left
				style.left = left;
				node.runtimeStyle.left = rsLeft;
			}
			if (ret === 'medium') {
				name = name.replace('Width', 'Style');
				//border width 默认值为medium，即使其为0'
				if (currentStyle[name] === 'none') {
					ret = '0px';
				}
			}
			return ret === '' ? 'auto' : border[ret] || ret;
		};
		cssHooks['opacity:set'] = function (node, name, value) {
			var style = node.style;

			var opacity = Number(value) <= 1 ? 'alpha(opacity=' + value * 100 + ')' : '';
			var filter = style.filter || '';
			style.zoom = 1;
			//不能使用以下方式设置透明度
			//node.filters.alpha.opacity = value * 100
			style.filter = (ralpha.test(filter) ? filter.replace(ralpha, opacity) : filter + ' ' + opacity).trim();

			if (!style.filter) {
				style.removeAttribute('filter');
			}
		};
		cssHooks['opacity:get'] = function (node) {
			var match = node.style.filter.match(ropactiy) || [];
			var ret = false;
			for (var i = 0, el; el = match[i++];) {
				if (el === 'opacity') {
					ret = true;
				} else if (ret) {
					return el / 100 + '';
				}
			}
			return '1'; //确保返回的是字符串
		};
	}

	/* istanbul ignore next */
	avalon.fn.offset = function () {
		//取得距离页面左右角的坐标
		var node = this[0],
			box = {
				left: 0,
				top: 0
			};
		if (!node || !node.tagName || !node.ownerDocument) {
			return box;
		}
		var doc = node.ownerDocument;
		var body = doc.body;
		var root$$1 = doc.documentElement;
		var win = doc.defaultView || doc.parentWindow;
		if (!avalon.contains(root$$1, node)) {
			return box;
		}
		//http://hkom.blog1.fc2.com/?mode=m&no=750 body的偏移量是不包含margin的
		//我们可以通过getBoundingClientRect来获得元素相对于client的rect.
		//http://msdn.microsoft.com/en-us/library/ms536433.aspx
		if (node.getBoundingClientRect) {
			box = node.getBoundingClientRect(); // BlackBerry 5, iOS 3 (original iPhone)
		}
		//chrome/IE6: body.scrollTop, firefox/other: root.scrollTop
		var clientTop = root$$1.clientTop || body.clientTop,
			clientLeft = root$$1.clientLeft || body.clientLeft,
			scrollTop = Math.max(win.pageYOffset || 0, root$$1.scrollTop, body.scrollTop),
			scrollLeft = Math.max(win.pageXOffset || 0, root$$1.scrollLeft, body.scrollLeft);
		// 把滚动距离加到left,top中去。
		// IE一些版本中会自动为HTML元素加上2px的border，我们需要去掉它
		// http://msdn.microsoft.com/en-us/library/ms533564(VS.85).aspx
		return {
			top: box.top + scrollTop - clientTop,
			left: box.left + scrollLeft - clientLeft
		};
	};

	//生成avalon.fn.scrollLeft, avalon.fn.scrollTop方法
	/* istanbul ignore next */
	avalon.each({
		scrollLeft: 'pageXOffset',
		scrollTop: 'pageYOffset'
	}, function (method, prop) {
		avalon.fn[method] = function (val) {
			var node = this[0] || {};
			var win = getWindow(node);
			var root$$1 = avalon.root;
			var top = method === 'scrollTop';
			if (!arguments.length) {
				return win ? prop in win ? win[prop] : root$$1[method] : node[method];
			} else {
				if (win) {
					win.scrollTo(!top ? val : avalon(win).scrollLeft(), top ? val : avalon(win).scrollTop());
				} else {
					node[method] = val;
				}
			}
		};
	});

	function getDuplexType(elem) {
		var ret = elem.tagName.toLowerCase();
		if (ret === 'input') {
			return rcheckedType.test(elem.type) ? 'checked' : elem.type;
		}
		return ret;
	}

	/**
	 * IE6/7/8中，如果option没有value值，那么将返回空字符串。
	 * IE9/Firefox/Safari/Chrome/Opera 中先取option的value值，如果没有value属性，则取option的innerText值。
	 * IE11及W3C，如果没有指定value，那么node.value默认为node.text（存在trim作），但IE9-10则是取innerHTML(没trim操作)
	 */

	function getOption(node) {
		if (node.hasAttribute && node.hasAttribute('value')) {
			return node.getAttribute('value');
		}
		var attr = node.getAttributeNode('value');
		if (attr && attr.specified) {
			return attr.value;
		}
		return node.innerHTML.trim();
	}

	var valHooks = {
		'option:get': msie$1 ? getOption : function (node) {
			return node.value;
		},
		'select:get': function selectGet(node, value) {
			var option,
				options = node.options,
				index = node.selectedIndex,
				getter = valHooks['option:get'],
				one = node.type === 'select-one' || index < 0,
				values = one ? null : [],
				max = one ? index + 1 : options.length,
				i = index < 0 ? max : one ? index : 0;
			for (; i < max; i++) {
				option = options[i];
				//IE6-9在reset后不会改变selected，需要改用i === index判定
				//我们过滤所有disabled的option元素，但在safari5下，
				//如果设置optgroup为disable，那么其所有孩子都disable
				//因此当一个元素为disable，需要检测其是否显式设置了disable及其父节点的disable情况
				if ((option.selected || i === index) && !option.disabled && (!option.parentNode.disabled || option.parentNode.tagName !== 'OPTGROUP')) {
					value = getter(option);
					if (one) {
						return value;
					}
					//收集所有selected值组成数组返回
					values.push(value);
				}
			}
			return values;
		},
		'select:set': function selectSet(node, values, optionSet) {
			values = [].concat(values); //强制转换为数组
			var getter = valHooks['option:get'];
			for (var i = 0, el; el = node.options[i++];) {
				if (el.selected = values.indexOf(getter(el)) > -1) {
					optionSet = true;
				}
			}
			if (!optionSet) {
				node.selectedIndex = -1;
			}
		}
	};

	avalon.fn.val = function (value) {
		var node = this[0];
		if (node && node.nodeType === 1) {
			var get = arguments.length === 0;
			var access = get ? ':get' : ':set';
			var fn = valHooks[getDuplexType(node) + access];
			if (fn) {
				var val = fn(node, value);
			} else if (get) {
				return (node.value || '').replace(/\r/g, '');
			} else {
				node.value = value;
			}
		}
		return get ? val : this;
	};

	var voidTag = {
		area: 1,
		base: 1,
		basefont: 1,
		bgsound: 1,
		br: 1,
		col: 1,
		command: 1,
		embed: 1,
		frame: 1,
		hr: 1,
		img: 1,
		input: 1,
		keygen: 1,
		link: 1,
		meta: 1,
		param: 1,
		source: 1,
		track: 1,
		wbr: 1
	};

	function makeObject(str) {
		return oneObject(str + ',template,#document-fragment,#comment');
	}
	var pNestChild = oneObject('div,ul,ol,dl,table,h1,h2,h3,h4,h5,h6,form,fieldset');
	var tNestChild = makeObject('tr,style,script');
	var nestObject = {
		p: pNestChild,
		// https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-inselect
		select: makeObject('option,optgroup,#text'),
		optgroup: makeObject('option,#text'),
		option: makeObject('#text'),
		// https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-intd
		// https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-incaption
		// No special behavior since these rules fall back to "in body" mode for
		// all except special table nodes which cause bad parsing behavior anyway.

		// https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-intr
		tr: makeObject('th,td,style,script'),

		// https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-intbody
		tbody: tNestChild,
		tfoot: tNestChild,
		thead: tNestChild,
		// https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-incolgroup
		colgroup: makeObject('col'),
		// https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-intable
		// table: oneObject('caption,colgroup,tbody,thead,tfoot,style,script,template,#document-fragment'),
		// https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-inhead
		head: makeObject('base,basefont,bgsound,link,style,script,meta,title,noscript,noframes'),
		// https://html.spec.whatwg.org/multipage/semantics.html#the-html-element
		html: oneObject('head,body')
	};

	/**
	 * ------------------------------------------------------------
	 * avalon2.2.6的新式lexer
	 * 将字符串变成一个虚拟DOM树,方便以后进一步变成模板函数
	 * 此阶段只会生成VElement,VText,VComment
	 * ------------------------------------------------------------
	 */
	var specalTag = { xmp: 1, style: 1, script: 1, noscript: 1, textarea: 1, '#comment': 1, template: 1 };
	var hiddenTag = { style: 1, script: 1, noscript: 1, template: 1 };
	var rcontent = /\S/; //判定里面有没有内容
	var rsp = /\s/;
	function fromString(str) {
		return from(str);
	}
	avalon.lexer = fromString;

	var strCache = new Cache(100);

	function from(str) {
		var cacheKey = str;
		var cached = strCache.get(cacheKey);
		if (cached) {
			return avalon.mix(true, [], cached);
		}

		var ret = parse(str, false);
		strCache.put(cacheKey, avalon.mix(true, [], ret));
		return ret;
	}

	/**
	 *
	 *
	 * @param {any} string
	 * @param {any} getOne 只返回一个节点
	 * @returns
	 */
	function parse(string, getOne) {
		getOne = getOne === void 666 || getOne === true;
		var ret = lexer(string, getOne);
		if (getOne) {
			return typeof ret[0] === 'string' ? ret[1] : ret[0];
		}
		return ret;
	}

	function lexer(string, getOne) {
		var tokens = [];
		var breakIndex = 9990;
		var stack = [];
		var origString = string;
		var origLength = string.length;

		stack.last = function () {
			return stack[stack.length - 1];
		};
		var ret = [];

		function addNode(node) {
			var p = stack.last();
			if (p && p.children) {
				p.children.push(node);
			} else {
				ret.push(node);
			}
		}

		var lastNode;
		do {
			if (--breakIndex === 0) {
				break;
			}
			var arr = getCloseTag(string);

			if (arr) {
				//处理关闭标签
				string = string.replace(arr[0], '');
				var _node = stack.pop();
				if (!_node) {
					throw '是不是有属性值没有用引号括起';
				}
				//处理下面两种特殊情况：
				//1. option会自动移除元素节点，将它们的nodeValue组成新的文本节点
				//2. table会将没有被thead, tbody, tfoot包起来的tr或文本节点，收集到一个新的tbody元素中

				if (_node.nodeName === 'option') {
					_node.children = [{
						nodeName: '#text',
						nodeValue: getText(_node)
					}];
				} else if (_node.nodeName === 'table') {
					insertTbody(_node.children);
				}
				lastNode = null;
				if (getOne && ret.length === 1 && !stack.length) {
					return [origString.slice(0, origLength - string.length), ret[0]];
				}
				continue;
			}

			var arr = getOpenTag(string);
			if (arr) {
				string = string.replace(arr[0], '');
				var node = arr[1];
				addNode(node);
				var selfClose = !!(node.isVoidTag || specalTag[node.nodeName]);
				if (!selfClose) {
					//放到这里可以添加孩子
					stack.push(node);
				}
				if (getOne && selfClose && !stack.length) {
					return [origString.slice(0, origLength - string.length), node];
				}
				lastNode = node;
				continue;
			}

			var text = '';
			do {
				//处理<div><<<<<<div>的情况
				var _index = string.indexOf('<');
				if (_index === 0) {
					text += string.slice(0, 1);
					string = string.slice(1);
				} else {
					break;
				}
			} while (string.length);

			//处理<div>{aaa}</div>,<div>xxx{aaa}xxx</div>,<div>xxx</div>{aaa}sss的情况
			var index = string.indexOf('<'); //判定它后面是否存在标签
			if (index === -1) {
				text = string;
				string = '';
			} else {
				var openIndex = string.indexOf(config.openTag);

				if (openIndex !== -1 && openIndex < index) {
					if (openIndex !== 0) {
						text += string.slice(0, openIndex);
					}
					var dirString = string.slice(openIndex);
					var textDir = parseTextDir(dirString);
					text += textDir;
					string = dirString.slice(textDir.length);
				} else {
					text += string.slice(0, index);
					string = string.slice(index);
				}
			}
			var mayNode = addText(lastNode, text, addNode);
			if (mayNode) {
				lastNode = mayNode;
			}
		} while (string.length);
		return ret;
	}

	function addText(lastNode, text, addNode) {
		if (rcontent.test(text)) {
			if (lastNode && lastNode.nodeName === '#text') {
				lastNode.nodeValue += text;
				return lastNode;
			} else {
				lastNode = {
					nodeName: '#text',
					nodeValue: text
				};
				addNode(lastNode);
				return lastNode;
			}
		}
	}

	function parseTextDir(string) {
		var closeTag = config.closeTag;
		var openTag = config.openTag;
		var closeTagFirst = closeTag.charAt(0);
		var closeTagLength = closeTag.length;
		var state = 'code',
			quote$$1,
			escape;
		for (var i = openTag.length, n = string.length; i < n; i++) {

			var c = string.charAt(i);
			switch (state) {
				case 'code':
					if (c === '"' || c === "'") {
						state = 'string';
						quote$$1 = c;
					} else if (c === closeTagFirst) {
						//如果遇到}
						if (string.substr(i, closeTagLength) === closeTag) {
							return string.slice(0, i + closeTagLength);
						}
					}
					break;
				case 'string':
					if (c === '\\' && /"'/.test(string.charAt(i + 1))) {
						escape = !escape;
					}
					if (c === quote$$1 && !escape) {
						state = 'code';
					}
					break;
			}
		}
		throw '找不到界定符' + closeTag;
	}

	var rtbody = /^(tbody|thead|tfoot)$/;

	function insertTbody(nodes) {
		var tbody = false;
		for (var i = 0, n = nodes.length; i < n; i++) {
			var node = nodes[i];
			if (rtbody.test(node.nodeName)) {
				tbody = false;
				continue;
			}

			if (node.nodeName === 'tr') {
				if (tbody) {
					nodes.splice(i, 1);
					tbody.children.push(node);
					n--;
					i--;
				} else {
					tbody = {
						nodeName: 'tbody',
						props: {},
						children: [node]
					};
					nodes.splice(i, 1, tbody);
				}
			} else {
				if (tbody) {
					nodes.splice(i, 1);
					tbody.children.push(node);
					n--;
					i--;
				}
			}
		}
	}

	//<div>{{<div/>}}</div>
	function getCloseTag(string) {
		if (string.indexOf("</") === 0) {
			var match = string.match(/\<\/(\w+[^\s\/\>]*)>/);
			if (match) {
				var tag = match[1];
				string = string.slice(3 + tag.length);
				return [match[0], {
					nodeName: tag
				}];
			}
		}
		return null;
	}
	var ropenTag = /\<(\w[^\s\/\>]*)/;

	function getOpenTag(string) {
		if (string.indexOf("<") === 0) {
			var i = string.indexOf('<!--'); //处理注释节点
			if (i === 0) {
				var l = string.indexOf('-->');
				if (l === -1) {
					thow('注释节点没有闭合 ' + string.slice(0, 100));
				}
				var node = {
					nodeName: '#comment',
					nodeValue: string.slice(4, l)
				};
				return [string.slice(0, l + 3), node];
			}
			var match = string.match(ropenTag); //处理元素节点
			if (match) {
				var leftContent = match[0],
					tag = match[1];
				var node = {
					nodeName: tag,
					props: {},
					children: []
				};

				string = string.replace(leftContent, ''); //去掉标签名(rightContent)
				try {
					var arr = getAttrs(string); //处理属性
				} catch (e) {}
				if (arr) {
					node.props = arr[1];
					string = string.replace(arr[0], '');
					leftContent += arr[0];
				}

				if (string.charAt(0) === '>') {
					//处理开标签的边界符
					leftContent += '>';
					string = string.slice(1);
					if (voidTag[node.nodeName]) {
						node.isVoidTag = true;
					}
				} else if (string.slice(0, 2) === '/>') {
					//处理开标签的边界符
					leftContent += '/>';
					string = string.slice(2);
					node.isVoidTag = true;
				}

				if (!node.isVoidTag && specalTag[tag]) {
					//如果是script, style, xmp等元素
					var closeTag = '</' + tag + '>';
					var j = string.indexOf(closeTag);
					var nodeValue = string.slice(0, j);
					leftContent += nodeValue + closeTag;
					node.children.push({
						nodeName: '#text',
						nodeValue: nodeValue
					});
					if (tag === 'textarea') {
						node.props.type = tag;
						node.props.value = nodeValue;
					}
				}
				return [leftContent, node];
			}
		}
	}

	function getText(node) {
		var ret = '';
		node.children.forEach(function (el) {
			if (el.nodeName === '#text') {
				ret += el.nodeValue;
			} else if (el.children && !hiddenTag[el.nodeName]) {
				ret += getText(el);
			}
		});
		return ret;
	}

	function getAttrs(string) {
		var state = 'AttrName',
			attrName = '',
			attrValue = '',
			quote$$1,
			escape,
			props = {};
		for (var i = 0, n = string.length; i < n; i++) {
			var c = string.charAt(i);
			switch (state) {
				case 'AttrName':
					if (c === '/' && string.charAt(i + 1) === '>' || c === '>') {
						if (attrName) props[attrName] = attrName;
						return [string.slice(0, i), props];
					}
					if (rsp.test(c)) {
						if (attrName) {
							state = 'AttrEqual';
						}
					} else if (c === '=') {
						if (!attrName) {
							throw '必须指定属性名';
						}
						state = 'AttrQuote';
					} else {
						attrName += c;
					}
					break;
				case 'AttrEqual':
					if (c === '=') {
						state = 'AttrQuote';
					} else if (rcontent.test(c)) {
						props[attrName] = attrName;
						attrName = c;
						state = 'AttrName';
					}
					break;
				case 'AttrQuote':
					if (c === '"' || c === "'") {
						quote$$1 = c;
						state = 'AttrValue';
						escape = false;
					}
					break;
				case 'AttrValue':
					if (c === '\\' && /"'/.test(string.charAt(i + 1))) {
						escape = !escape;
					}
					if (c === '\n') {
						break;
					}
					if (c !== quote$$1) {
						attrValue += c;
					} else if (c === quote$$1 && !escape) {
						props[attrName] = attrValue;
						attrName = attrValue = '';
						state = 'AttrName';
					}
					break;
			}
		}
		throw '必须关闭标签';
	}

	var rhtml = /<|&#?\w+;/;
	var htmlCache = new Cache(128);
	var rxhtml = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig;

	avalon.parseHTML = function (html) {
		var fragment = createFragment();
		//处理非字符串
		if (typeof html !== 'string') {
			return fragment;
		}
		//处理非HTML字符串
		if (!rhtml.test(html)) {
			return document$1.createTextNode(html);
		}

		html = html.replace(rxhtml, '<$1></$2>').trim();
		var hasCache = htmlCache.get(html);
		if (hasCache) {
			return avalon.cloneNode(hasCache);
		}
		var vnodes = fromString(html);
		for (var i = 0, el; el = vnodes[i++];) {
			var child = avalon.vdom(el, 'toDOM');
			fragment.appendChild(child);
		}
		if (html.length < 1024) {
			htmlCache.put(html, fragment);
		}
		return fragment;
	};

	avalon.innerHTML = function (node, html) {
		var parsed = avalon.parseHTML(html);
		this.clearHTML(node);
		node.appendChild(parsed);
	};

	//https://github.com/karloespiritu/escapehtmlent/blob/master/index.js
	avalon.unescapeHTML = function (html) {
		return String(html).replace(/&quot;/g, '"').replace(/&#39;/g, '\'').replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&amp;/g, '&');
	};

	avalon.clearHTML = function (node) {
		/* istanbul ignore next */
		while (node.lastChild) {
			node.removeChild(node.lastChild);
		}
		return node;
	};

	//http://www.feiesoft.com/html/events.html
	//http://segmentfault.com/q/1010000000687977/a-1020000000688757
	var canBubbleUp = {
		click: true,
		dblclick: true,
		keydown: true,
		keypress: true,
		keyup: true,
		mousedown: true,
		mousemove: true,
		mouseup: true,
		mouseover: true,
		mouseout: true,
		wheel: true,
		mousewheel: true,
		input: true,
		change: true,
		beforeinput: true,
		compositionstart: true,
		compositionupdate: true,
		compositionend: true,
		select: true,
		//http://blog.csdn.net/lee_magnum/article/details/17761441
		cut: true,
		copy: true,
		paste: true,
		beforecut: true,
		beforecopy: true,
		beforepaste: true,
		focusin: true,
		focusout: true,
		DOMFocusIn: true,
		DOMFocusOut: true,
		DOMActivate: true,
		dragend: true,
		datasetchanged: true
	};

	/* istanbul ignore if */
	var hackSafari = avalon.modern && document$1.ontouchstart;

	//添加fn.bind, fn.unbind, bind, unbind
	avalon.fn.bind = function (type, fn, phase) {
		if (this[0]) {
			//此方法不会链
			return avalon.bind(this[0], type, fn, phase);
		}
	};

	avalon.fn.unbind = function (type, fn, phase) {
		if (this[0]) {
			var args = _slice.call(arguments);
			args.unshift(this[0]);
			avalon.unbind.apply(0, args);
		}
		return this;
	};

	/*绑定事件*/
	avalon.bind = function (elem, type, fn) {
		if (elem.nodeType === 1) {
			var value = elem.getAttribute('avalon-events') || '';
			//如果是使用ms-on-*绑定的回调,其uuid格式为e12122324,
			//如果是使用bind方法绑定的回调,其uuid格式为_12
			var uuid = getShortID(fn);
			var hook = eventHooks[type];
			/* istanbul ignore if */
			if (type === 'click' && hackSafari) {
				elem.addEventListener('click', avalon.noop);
			}
			/* istanbul ignore if */
			if (hook) {
				type = hook.type || type;
				if (hook.fix) {
					fn = hook.fix(elem, fn);
					fn.uuid = uuid;
				}
			}
			var key = type + ':' + uuid;
			avalon.eventListeners[fn.uuid] = fn;
			/* istanbul ignore if */
			if (value.indexOf(type + ':') === -1) {
				//同一种事件只绑定一次
				if (canBubbleUp[type] || avalon.modern && focusBlur[type]) {
					delegateEvent(type);
				} else {
					avalon._nativeBind(elem, type, dispatch);
				}
			}
			var keys = value.split(',');
			/* istanbul ignore if */
			if (keys[0] === '') {
				keys.shift();
			}
			if (keys.indexOf(key) === -1) {
				keys.push(key);
				setEventId(elem, keys.join(','));
				//将令牌放进avalon-events属性中
			}
			return fn;
		} else {
			/* istanbul ignore next */
			var cb = function cb(e) {
				fn.call(elem, new avEvent(e));
			};

			avalon._nativeBind(elem, type, cb);
			return cb;
		}
	};

	function setEventId(node, value) {
		node.setAttribute('avalon-events', value);
	}
	/* istanbul ignore next */
	avalon.unbind = function (elem, type, fn) {
		if (elem.nodeType === 1) {
			var value = elem.getAttribute('avalon-events') || '';
			switch (arguments.length) {
				case 1:
					avalon._nativeUnBind(elem, type, dispatch);
					elem.removeAttribute('avalon-events');
					break;
				case 2:
					value = value.split(',').filter(function (str) {
						return str.indexOf(type + ':') === -1;
					}).join(',');
					setEventId(elem, value);
					break;
				default:
					var search = type + ':' + fn.uuid;
					value = value.split(',').filter(function (str) {
						return str !== search;
					}).join(',');
					setEventId(elem, value);
					delete avalon.eventListeners[fn.uuid];
					break;
			}
		} else {
			avalon._nativeUnBind(elem, type, fn);
		}
	};

	var typeRegExp = {};

	function collectHandlers(elem, type, handlers) {
		var value = elem.getAttribute('avalon-events');
		if (value && (elem.disabled !== true || type !== 'click')) {
			var uuids = [];
			var reg = typeRegExp[type] || (typeRegExp[type] = new RegExp("\\b" + type + '\\:([^,\\s]+)', 'g'));
			value.replace(reg, function (a, b) {
				uuids.push(b);
				return a;
			});
			if (uuids.length) {
				handlers.push({
					elem: elem,
					uuids: uuids
				});
			}
		}
		elem = elem.parentNode;
		var g = avalon.gestureEvents || {};
		if (elem && elem.getAttribute && (canBubbleUp[type] || g[type])) {
			collectHandlers(elem, type, handlers);
		}
	}

	var rhandleHasVm = /^e/;

	function dispatch(event) {
		event = new avEvent(event);
		var type = event.type;
		var elem = event.target;
		var handlers = [];
		collectHandlers(elem, type, handlers);
		var i = 0,
			j,
			uuid,
			handler;
		while ((handler = handlers[i++]) && !event.cancelBubble) {
			var host = event.currentTarget = handler.elem;
			j = 0;
			while (uuid = handler.uuids[j++]) {
				if (event.stopImmediate) {
					break;
				}
				var fn = avalon.eventListeners[uuid];
				if (fn) {
					var vm = rhandleHasVm.test(uuid) ? handler.elem._ms_context_ : 0;
					if (vm && vm.$hashcode === false) {
						return avalon.unbind(elem, type, fn);
					}
					var ret = fn.call(vm || elem, event);

					if (ret === false) {
						event.preventDefault();
						event.stopPropagation();
					}
				}
			}
		}
	}

	var focusBlur = {
		focus: true,
		blur: true
	};

	function delegateEvent(type) {
		var value = root.getAttribute('delegate-events') || '';
		if (value.indexOf(type) === -1) {
			//IE6-8会多次绑定同种类型的同一个函数,其他游览器不会
			var arr = value.match(avalon.rword) || [];
			arr.push(type);
			root.setAttribute('delegate-events', arr.join(','));
			avalon._nativeBind(root, type, dispatch, !!focusBlur[type]);
		}
	}

	var eventProto = {
		webkitMovementY: 1,
		webkitMovementX: 1,
		keyLocation: 1,
		fixEvent: function fixEvent() {},
		preventDefault: function preventDefault() {
			var e = this.originalEvent || {};
			e.returnValue = this.returnValue = false;
			if (modern && e.preventDefault) {
				e.preventDefault();
			}
		},
		stopPropagation: function stopPropagation() {
			var e = this.originalEvent || {};
			e.cancelBubble = this.cancelBubble = true;
			if (modern && e.stopPropagation) {
				e.stopPropagation();
			}
		},
		stopImmediatePropagation: function stopImmediatePropagation() {
			this.stopPropagation();
			this.stopImmediate = true;
		},
		toString: function toString() {
			return '[object Event]'; //#1619
		}
	};

	function avEvent(event) {
		if (event.originalEvent) {
			return event;
		}
		for (var i in event) {
			if (!eventProto[i]) {
				this[i] = event[i];
			}
		}
		if (!this.target) {
			this.target = event.srcElement;
		}
		var target = this.target;
		this.fixEvent();
		this.timeStamp = new Date() - 0;
		this.originalEvent = event;
	}
	avEvent.prototype = eventProto;
	//针对firefox, chrome修正mouseenter, mouseleave
	/* istanbul ignore if */
	if (!('onmouseenter' in root)) {
		avalon.each({
			mouseenter: 'mouseover',
			mouseleave: 'mouseout'
		}, function (origType, fixType) {
			eventHooks[origType] = {
				type: fixType,
				fix: function fix(elem, fn) {
					return function (e) {
						var t = e.relatedTarget;
						if (!t || t !== elem && !(elem.compareDocumentPosition(t) & 16)) {
							delete e.type;
							e.type = origType;
							return fn.apply(this, arguments);
						}
					};
				}
			};
		});
	}
	//针对IE9+, w3c修正animationend
	avalon.each({
		AnimationEvent: 'animationend',
		WebKitAnimationEvent: 'webkitAnimationEnd'
	}, function (construct, fixType) {
		if (window$1[construct] && !eventHooks.animationend) {
			eventHooks.animationend = {
				type: fixType
			};
		}
	});

	/* istanbul ignore if */
	if (!("onmousewheel" in document$1)) {
		/* IE6-11 chrome mousewheel wheelDetla 下 -120 上 120
         firefox DOMMouseScroll detail 下3 上-3
         firefox wheel detlaY 下3 上-3
         IE9-11 wheel deltaY 下40 上-40
         chrome wheel deltaY 下100 上-100 */
		var fixWheelType = document$1.onwheel !== void 0 ? 'wheel' : 'DOMMouseScroll';
		var fixWheelDelta = fixWheelType === 'wheel' ? 'deltaY' : 'detail';
		eventHooks.mousewheel = {
			type: fixWheelType,
			fix: function fix(elem, fn) {
				return function (e) {
					var delta = e[fixWheelDelta] > 0 ? -120 : 120;
					e.wheelDelta = ~~elem._ms_wheel_ + delta;
					elem._ms_wheel_ = e.wheelDeltaY = e.wheelDelta;
					e.wheelDeltaX = 0;
					if (Object.defineProperty) {
						Object.defineProperty(e, 'type', {
							value: 'mousewheel'
						});
					}
					return fn.apply(this, arguments);
				};
			}
		};
	}

	/* istanbul ignore if */
	if (!modern) {
		delete canBubbleUp.change;
		delete canBubbleUp.select;
	}
	/* istanbul ignore next */
	avalon._nativeBind = modern ? function (el, type, fn, capture) {
		el.addEventListener(type, fn, !!capture);
	} : function (el, type, fn) {
		el.attachEvent('on' + type, fn);
	};
	/* istanbul ignore next */
	avalon._nativeUnBind = modern ? function (el, type, fn, a) {
		el.removeEventListener(type, fn, !!a);
	} : function (el, type, fn) {
		el.detachEvent('on' + type, fn);
	};
	/* istanbul ignore next */
	avalon.fireDom = function (elem, type, opts) {
		if (document$1.createEvent) {
			var hackEvent = document$1.createEvent('Events');
			hackEvent.initEvent(type, true, true, opts);
			avalon.shadowCopy(hackEvent, opts);
			elem.dispatchEvent(hackEvent);
		} else if (root.contains(elem)) {
			//IE6-8触发事件必须保证在DOM树中,否则报'SCRIPT16389: 未指明的错误'
			hackEvent = document$1.createEventObject();
			if (opts) avalon.shadowCopy(hackEvent, opts);
			try {
				elem.fireEvent('on' + type, hackEvent);
			} catch (e) {
				avalon.log('fireDom', type, 'args error');
			}
		}
	};

	var rmouseEvent = /^(?:mouse|contextmenu|drag)|click/;
	/* istanbul ignore next */
	avEvent.prototype.fixEvent = function () {
		var event = this;
		if (event.which == null && event.type.indexOf('key') === 0) {
			event.which = event.charCode != null ? event.charCode : event.keyCode;
		}
		if (rmouseEvent.test(event.type) && !('pageX' in event)) {
			var DOC = event.target.ownerDocument || document$1;
			var box = DOC.compatMode === 'BackCompat' ? DOC.body : DOC.documentElement;
			event.pageX = event.clientX + (box.scrollLeft >> 0) - (box.clientLeft >> 0);
			event.pageY = event.clientY + (box.scrollTop >> 0) - (box.clientTop >> 0);
			event.wheelDeltaY = ~~event.wheelDelta;
			event.wheelDeltaX = 0;
		}
	};

	//针对IE6-8修正input
	/* istanbul ignore if */
	if (!('oninput' in document$1.createElement('input'))) {
		eventHooks.input = {
			type: 'propertychange',
			fix: function fix(elem, fn) {
				return function (e) {
					if (e.propertyName === 'value') {
						e.type = 'input';
						return fn.apply(this, arguments);
					}
				};
			}
		};
	}

	var readyList = [];

	function fireReady(fn) {
		avalon.isReady = true;
		while (fn = readyList.shift()) {
			fn(avalon);
		}
	}

	avalon.ready = function (fn) {
		readyList.push(fn);
		if (avalon.isReady) {
			fireReady();
		}
	};

	avalon.ready(function () {
		avalon.scan && avalon.scan(document$1.body);
	});

	/* istanbul ignore next */
	function bootstrap() {
		function doScrollCheck() {
			try {
				//IE下通过doScrollCheck检测DOM树是否建完
				root.doScroll('left');
				fireReady();
			} catch (e) {
				setTimeout(doScrollCheck);
			}
		}
		if (document$1.readyState === 'complete') {
			setTimeout(fireReady); //如果在domReady之外加载
		} else if (document$1.addEventListener) {
			document$1.addEventListener('DOMContentLoaded', fireReady, false);
		} else if (document$1.attachEvent) {
			//必须传入三个参数，否则在firefox4-26中报错
			//caught exception: [Exception... "Not enough arguments"  nsresult: "0x
			document$1.attachEvent('onreadystatechange', function () {
				if (document$1.readyState === 'complete') {
					fireReady();
				}
			});
			try {
				var isTop = window$1.frameElement === null;
			} catch (e) {}
			if (root.doScroll && isTop && window$1.external) {
				//fix IE iframe BUG
				doScrollCheck();
			}
		}

		avalon.bind(window$1, 'load', fireReady);
	}
	if (inBrowser) {
		bootstrap();
	}

	/**
	 * ------------------------------------------------------------
	 *                          DOM Api
	 * shim,class,data,css,val,html,event,ready
	 * ------------------------------------------------------------
	 */

	var orphanTag = {
		script: 1,
		style: 1,
		textarea: 1,
		xmp: 1,
		noscript: 1,
		template: 1
	};

	/*
     *  此模块只用于文本转虚拟DOM,
     *  因为在真实浏览器会对我们的HTML做更多处理,
     *  如, 添加额外属性, 改变结构
     *  此模块就是用于模拟这些行为
     */
	function makeOrphan(node, nodeName, innerHTML) {
		switch (nodeName) {
			case 'style':
			case 'script':
			case 'noscript':
			case 'template':
			case 'xmp':
				node.children = [{
					nodeName: '#text',
					nodeValue: innerHTML
				}];
				break;
			case 'textarea':
				var props = node.props;
				props.type = nodeName;
				props.value = innerHTML;
				node.children = [{
					nodeName: '#text',
					nodeValue: innerHTML
				}];
				break;
			case 'option':
				node.children = [{
					nodeName: '#text',
					nodeValue: trimHTML(innerHTML)
				}];
				break;
		}
	}

	//专门用于处理option标签里面的标签
	var rtrimHTML = /<\w+(\s+("[^"]*"|'[^']*'|[^>])+)?>|<\/\w+>/gi;
	function trimHTML(v) {
		return String(v).replace(rtrimHTML, '').trim();
	}

	//widget rule duplex validate

	function fromDOM(dom) {
		return [from$1(dom)];
	}

	function from$1(node) {
		var type = node.nodeName.toLowerCase();
		switch (type) {
			case '#text':
			case '#comment':
				return {
					nodeName: type,
					dom: node,
					nodeValue: node.nodeValue
				};
			default:
				var props = markProps(node, node.attributes || []);
				var vnode = {
					nodeName: type,
					dom: node,
					isVoidTag: !!voidTag[type],
					props: props
				};
				if (type === 'option') {
					//即便你设置了option.selected = true,
					//option.attributes也找不到selected属性
					props.selected = node.selected;
				}
				if (orphanTag[type] || type === 'option') {
					makeOrphan(vnode, type, node.text || node.innerHTML);
					if (node.childNodes.length === 1) {
						vnode.children[0].dom = node.firstChild;
					}
				} else if (!vnode.isVoidTag) {
					vnode.children = [];
					for (var i = 0, el; el = node.childNodes[i++];) {
						var child = from$1(el);
						if (/\S/.test(child.nodeValue)) {
							vnode.children.push(child);
						}
					}
				}
				return vnode;
		}
	}

	var rformElement = /input|textarea|select/i;

	function markProps(node, attrs) {
		var ret = {};
		for (var i = 0, n = attrs.length; i < n; i++) {
			var attr = attrs[i];
			if (attr.specified) {
				//IE6-9不会将属性名变小写,比如它会将用户的contenteditable变成contentEditable
				ret[attr.name.toLowerCase()] = attr.value;
			}
		}
		if (rformElement.test(node.nodeName)) {
			ret.type = node.type;
			var a = node.getAttributeNode('value');
			if (a && /\S/.test(a.value)) {
				//IE6,7中无法取得checkbox,radio的value
				ret.value = a.value;
			}
		}
		var style = node.style.cssText;
		if (style) {
			ret.style = style;
		}
		//类名 = 去重(静态类名+动态类名+ hover类名? + active类名)
		if (ret.type === 'select-one') {
			ret.selectedIndex = node.selectedIndex;
		}
		return ret;
	}

	function VText(text) {
		this.nodeName = '#text';
		this.nodeValue = text;
	}

	VText.prototype = {
		constructor: VText,
		toDOM: function toDOM() {
			/* istanbul ignore if*/
			if (this.dom) return this.dom;
			var v = avalon._decode(this.nodeValue);
			return this.dom = document$1.createTextNode(v);
		},
		toHTML: function toHTML() {
			return this.nodeValue;
		}
	};

	function VComment(text) {
		this.nodeName = '#comment';
		this.nodeValue = text;
	}
	VComment.prototype = {
		constructor: VComment,
		toDOM: function toDOM() {
			if (this.dom) return this.dom;
			return this.dom = document$1.createComment(this.nodeValue);
		},
		toHTML: function toHTML() {
			return '<!--' + this.nodeValue + '-->';
		}
	};

	function VElement(type, props, children, isVoidTag) {
		this.nodeName = type;
		this.props = props;
		this.children = children;
		this.isVoidTag = isVoidTag;
	}
	VElement.prototype = {
		constructor: VElement,
		toDOM: function toDOM() {
			if (this.dom) return this.dom;
			var dom,
				tagName = this.nodeName;
			if (avalon.modern && svgTags[tagName]) {
				dom = createSVG(tagName);
				/* istanbul ignore next*/
			} else if (!avalon.modern && (VMLTags[tagName] || rvml.test(tagName))) {
				dom = createVML(tagName);
			} else {
				dom = document$1.createElement(tagName);
			}

			var props = this.props || {};

			for (var i in props) {
				var val = props[i];
				if (skipFalseAndFunction(val)) {
					/* istanbul ignore if*/
					if (specalAttrs[i] && avalon.msie < 8) {
						specalAttrs[i](dom, val);
					} else {
						dom.setAttribute(i, val + '');
					}
				}
			}
			var c = this.children || [];
			var template = c[0] ? c[0].nodeValue : '';
			switch (this.nodeName) {
				case 'script':
					dom.type = 'noexec';
					dom.text = template;
					try {
						dom.innerHTML = template;
					} catch (e) {}
					dom.type = props.type || '';
					break;
				case 'noscript':
					dom.textContent = template;
				case 'style':
				case 'xmp':
				case 'template':
					try {
						dom.innerHTML = template;
					} catch (e) {
						/* istanbul ignore next*/
						hackIE(dom, this.nodeName, template);
					}
					break;
				case 'option':
					//IE6-8,为option添加文本子节点,不会同步到text属性中
					/* istanbul ignore next */
					if (msie$1 < 9) dom.text = template;
				default:
					/* istanbul ignore next */
					if (!this.isVoidTag && this.children) {
						this.children.forEach(function (el) {
							return c && dom.appendChild(avalon.vdom(c, 'toDOM'));
						});
					}
					break;
			}
			return this.dom = dom;
		},

		/* istanbul ignore next */

		toHTML: function toHTML() {
			var arr = [];
			var props = this.props || {};
			for (var i in props) {
				var val = props[i];
				if (skipFalseAndFunction(val)) {
					arr.push(i + '=' + avalon.quote(props[i] + ''));
				}
			}
			arr = arr.length ? ' ' + arr.join(' ') : '';
			var str = '<' + this.nodeName + arr;
			if (this.isVoidTag) {
				return str + '/>';
			}
			str += '>';
			if (this.children) {
				str += this.children.map(function (el) {
					return el ? avalon.vdom(el, 'toHTML') : '';
				}).join('');
			}
			return str + '</' + this.nodeName + '>';
		}
	};
	function hackIE(dom, nodeName, template) {
		switch (nodeName) {
			case 'style':
				dom.setAttribute('type', 'text/css');
				dom.styleSheet.cssText = template;
				break;
			case 'xmp': //IE6-8,XMP元素里面只能有文本节点,不能使用innerHTML
			case 'noscript':
				dom.textContent = template;
				break;
		}
	}
	function skipFalseAndFunction(a) {
		return a !== false && Object(a) !== a;
	}
	/* istanbul ignore next */
	var specalAttrs = {
		"class": function _class(dom, val) {
			dom.className = val;
		},
		style: function style(dom, val) {
			dom.style.cssText = val;
		},
		type: function type(dom, val) {
			try {
				//textarea,button 元素在IE6,7设置 type 属性会抛错
				dom.type = val;
			} catch (e) {}
		},
		'for': function _for(dom, val) {
			dom.setAttribute('for', val);
			dom.htmlFor = val;
		}
	};

	function createSVG(type) {
		return document$1.createElementNS('http://www.w3.org/2000/svg', type);
	}
	var svgTags = avalon.oneObject('circle,defs,ellipse,image,line,' + 'path,polygon,polyline,rect,symbol,text,use,g,svg');

	var rvml = /^\w+\:\w+/;
	/* istanbul ignore next*/
	function createVML(type) {
		if (document$1.styleSheets.length < 31) {
			document$1.createStyleSheet().addRule(".rvml", "behavior:url(#default#VML)");
		} else {
			// no more room, add to the existing one
			// http://msdn.microsoft.com/en-us/library/ms531194%28VS.85%29.aspx
			document$1.styleSheets[0].addRule(".rvml", "behavior:url(#default#VML)");
		}
		var arr = type.split(':');
		if (arr.length === 1) {
			arr.unshift('v');
		}
		var tag = arr[1];
		var ns = arr[0];
		if (!document$1.namespaces[ns]) {
			document$1.namespaces.add(ns, "urn:schemas-microsoft-com:vml");
		}
		return document$1.createElement('<' + ns + ':' + tag + ' class="rvml">');
	}

	var VMLTags = avalon.oneObject('shape,line,polyline,rect,roundrect,oval,arc,' + 'curve,background,image,shapetype,group,fill,' + 'stroke,shadow, extrusion, textbox, imagedata, textpath');

	function VFragment(children, key, val, index) {
		this.nodeName = '#document-fragment';
		this.children = children;
		this.key = key;
		this.val = val;
		this.index = index;
		this.props = {};
	}
	VFragment.prototype = {
		constructor: VFragment,
		toDOM: function toDOM() {
			if (this.dom) return this.dom;
			var f = this.toFragment();
			//IE6-11 docment-fragment都没有children属性
			this.split = f.lastChild;
			return this.dom = f;
		},
		dispose: function dispose() {
			this.toFragment();
			this.innerRender && this.innerRender.dispose();
			for (var i in this) {
				this[i] = null;
			}
		},
		toFragment: function toFragment() {
			var f = createFragment();
			this.children.forEach(function (el) {
				return f.appendChild(avalon.vdom(el, 'toDOM'));
			});
			return f;
		},
		toHTML: function toHTML() {
			var c = this.children;
			return c.map(function (el) {
				return avalon.vdom(el, 'toHTML');
			}).join('');
		}
	};

	/**
	 * 虚拟DOM的4大构造器
	 */
	avalon.mix(avalon, {
		VText: VText,
		VComment: VComment,
		VElement: VElement,
		VFragment: VFragment
	});

	var constNameMap = {
		'#text': 'VText',
		'#document-fragment': 'VFragment',
		'#comment': 'VComment'
	};

	var vdom = avalon.vdomAdaptor = avalon.vdom = function (obj, method) {
		if (!obj) {
			//obj在ms-for循环里面可能是null
			return method === "toHTML" ? '' : createFragment();
		}
		var nodeName = obj.nodeName;
		if (!nodeName) {
			return new avalon.VFragment(obj)[method]();
		}
		var constName = constNameMap[nodeName] || 'VElement';
		return avalon[constName].prototype[method].call(obj);
	};

	avalon.domize = function (a) {
		return avalon.vdom(a, 'toDOM');
	};

	avalon.pendingActions = [];
	avalon.uniqActions = {};
	avalon.inTransaction = 0;
	config.trackDeps = false;
	avalon.track = function () {
		if (config.trackDeps) {
			avalon.log.apply(avalon, arguments);
		}
	};

	/**
	 * Batch is a pseudotransaction, just for purposes of memoizing ComputedValues when nothing else does.
	 * During a batch `onBecomeUnobserved` will be called at most once per observable.
	 * Avoids unnecessary recalculations.
	 */

	function runActions() {
		if (avalon.isRunningActions === true || avalon.inTransaction > 0) return;
		avalon.isRunningActions = true;
		var tasks = avalon.pendingActions.splice(0, avalon.pendingActions.length);
		for (var i = 0, task; task = tasks[i++];) {
			task.update();
			delete avalon.uniqActions[task.uuid];
		}
		avalon.isRunningActions = false;
	}

	function propagateChanged(target) {
		var list = target.observers;
		for (var i = 0, el; el = list[i++];) {
			el.schedule(); //通知action, computed做它们该做的事
		}
	}

	//将自己抛到市场上卖
	function reportObserved(target) {
		var action = avalon.trackingAction || null;
		if (action !== null) {

			avalon.track('征收到', target.expr);
			action.mapIDs[target.uuid] = target;
		}
	}

	var targetStack = [];

	function collectDeps(action, getter) {
		if (!action.observers) return;
		var preAction = avalon.trackingAction;
		if (preAction) {
			targetStack.push(preAction);
		}
		avalon.trackingAction = action;
		avalon.track('【action】', action.type, action.expr, '开始征收依赖项');
		//多个observe持有同一个action
		action.mapIDs = {}; //重新收集依赖
		var hasError = true,
			result;
		try {
			result = getter.call(action);
			hasError = false;
		} finally {
			if (hasError) {
				avalon.warn('collectDeps fail', getter + '');
				action.mapIDs = {};
				avalon.trackingAction = preAction;
			} else {
				// 确保它总是为null
				avalon.trackingAction = targetStack.pop();
				try {
					resetDeps(action);
				} catch (e) {
					avalon.warn(e);
				}
			}
			return result;
		}
	}

	function resetDeps(action) {
		var prev = action.observers,
			curr = [],
			checked = {},
			ids = [];
		for (var i in action.mapIDs) {
			var dep = action.mapIDs[i];
			if (!dep.isAction) {
				if (!dep.observers) {
					//如果它已经被销毁
					delete action.mapIDs[i];
					continue;
				}
				ids.push(dep.uuid);
				curr.push(dep);
				checked[dep.uuid] = 1;
				if (dep.lastAccessedBy === action.uuid) {
					continue;
				}
				dep.lastAccessedBy = action.uuid;
				avalon.Array.ensure(dep.observers, action);
			}
		}
		var ids = ids.sort().join(',');
		if (ids === action.ids) {
			return;
		}
		action.ids = ids;
		if (!action.isComputed) {
			action.observers = curr;
		} else {
			action.depsCount = curr.length;
			action.deps = avalon.mix({}, action.mapIDs);
			action.depsVersion = {};
			for (var _i in action.mapIDs) {
				var _dep = action.mapIDs[_i];
				action.depsVersion[_dep.uuid] = _dep.version;
			}
		}

		for (var _i2 = 0, _dep2; _dep2 = prev[_i2++];) {
			if (!checked[_dep2.uuid]) {
				avalon.Array.remove(_dep2.observers, action);
			}
		}
	}

	function transaction(action, thisArg, args) {
		args = args || [];
		var name = 'transaction ' + (action.name || action.displayName || 'noop');
		transactionStart(name);
		var res = action.apply(thisArg, args);
		transactionEnd(name);
		return res;
	}
	avalon.transaction = transaction;

	function transactionStart(name) {
		avalon.inTransaction += 1;
	}

	function transactionEnd(name) {
		if (--avalon.inTransaction === 0) {
			avalon.isRunningActions = false;
			runActions();
		}
	}

	/*
     * 将要检测的字符串的字符串替换成??123这样的格式
     */
	var stringNum = 0;
	var stringPool = {
		map: {}
	};
	var rfill = /\?\?\d+/g;
	function dig(a) {
		var key = '??' + stringNum++;
		stringPool.map[key] = a;
		return key + ' ';
	}
	function fill(a) {
		var val = stringPool.map[a];
		return val;
	}
	function clearString(str) {
		var array = readString(str);
		for (var i = 0, n = array.length; i < n; i++) {
			str = str.replace(array[i], dig);
		}
		return str;
	}
	//https://github.com/RubyLouvre/avalon/issues/1944
	function readString(str, i, ret) {
		var end = false,
			s = 0,
			i = i || 0;
		ret = ret || [];
		for (var n = str.length; i < n; i++) {
			var c = str.charAt(i);
			if (!end) {
				if (c === "'") {
					end = "'";
					s = i;
				} else if (c === '"') {
					end = '"';
					s = i;
				}
			} else {
				if (c === end) {
					ret.push(str.slice(s, i + 1));
					end = false;
				}
			}
		}
		if (end !== false) {
			return readString(str, s + 1, ret);
		}
		return ret;
	}

	var keyMap = avalon.oneObject("break,case,catch,continue,debugger,default,delete,do,else,false," + "finally,for,function,if,in,instanceof,new,null,return,switch,this," + "throw,true,try,typeof,var,void,while,with," + /* 关键字*/
		"abstract,boolean,byte,char,class,const,double,enum,export,extends," + "final,float,goto,implements,import,int,interface,long,native," + "package,private,protected,public,short,static,super,synchronized," + "throws,transient,volatile,arguments");

	var skipMap = avalon.mix({
		Math: 1,
		Date: 1,
		$event: 1,
		window: 1,
		__vmodel__: 1,
		avalon: 1
	}, keyMap);

	var rvmKey = /(^|[^\w\u00c0-\uFFFF_])(@|##)(?=[$\w])/g;
	var ruselessSp = /\s*(\.|\|)\s*/g;
	var rshortCircuit = /\|\|/g;
	var brackets = /\(([^)]*)\)/;
	var rpipeline = /\|(?=\?\?)/;
	var rregexp = /(^|[^/])\/(?!\/)(\[.+?]|\\.|[^/\\\r\n])+\/[gimyu]{0,5}(?=\s*($|[\r\n,.;})]))/g;
	var robjectProp = /\.[\w\.\$]+/g; //对象的属性 el.xxx 中的xxx
	var robjectKey = /(\{|\,)\s*([\$\w]+)\s*:/g; //对象的键名与冒号 {xxx:1,yyy: 2}中的xxx, yyy
	var rfilterName = /\|(\w+)/g;
	var rlocalVar = /[$a-zA-Z_][$a-zA-Z0-9_]*/g;

	var exprCache = new Cache(300);

	function addScopeForLocal(str) {
		return str.replace(robjectProp, dig).replace(rlocalVar, function (el) {
			if (!skipMap[el]) {
				return "__vmodel__." + el;
			}
			return el;
		});
	}

	function addScope(expr, type) {
		var cacheKey = expr + ':' + type;
		var cache = exprCache.get(cacheKey);
		if (cache) {
			return cache.slice(0);
		}

		stringPool.map = {};
		//https://github.com/RubyLouvre/avalon/issues/1849
		var input = expr.replace(rregexp, function (a, b) {
			return b + dig(a.slice(b.length));
		}); //移除所有正则
		input = clearString(input); //移除所有字符串
		input = input.replace(rshortCircuit, dig). //移除所有短路运算符
		replace(ruselessSp, '$1'). //移除.|两端空白

		replace(robjectKey, function (_, a, b) {
			//移除所有键名
			return a + dig(b) + ':'; //比如 ms-widget="[{is:'ms-address-wrap', $id:'address'}]"这样极端的情况
		}).replace(rvmKey, '$1__vmodel__.'). //转换@与##为__vmodel__
		replace(rfilterName, function (a, b) {
			//移除所有过滤器的名字
			return '|' + dig(b);
		});
		input = addScopeForLocal(input); //在本地变量前添加__vmodel__

		var filters = input.split(rpipeline); //根据管道符切割表达式
		var body = filters.shift().replace(rfill, fill).trim();
		if (/\?\?\d/.test(body)) {
			body = body.replace(rfill, fill);
		}
		if (filters.length) {
			filters = filters.map(function (filter) {
				var bracketArgs = '';
				filter = filter.replace(brackets, function (a, b) {
					if (/\S/.test(b)) {
						bracketArgs += ',' + b; //还原字符串,正则,短路运算符
					}
					return '';
				});
				var arg = '[' + avalon.quote(filter.trim()) + bracketArgs + ']';
				return arg;
			});
			filters = 'avalon.composeFilters(' + filters + ')(__value__)';
			filters = filters.replace(rfill, fill);
		} else {
			filters = '';
		}
		return exprCache.put(cacheKey, [body, filters]);
	}
	var rhandleName = /^__vmodel__\.[$\w\.]+$/;
	var rfixIE678 = /__vmodel__\.([^(]+)\(([^)]*)\)/;
	function makeHandle(body) {
		if (rhandleName.test(body)) {
			body = body + '($event)';
		}
		/* istanbul ignore if */
		if (msie$1 < 9) {
			body = body.replace(rfixIE678, function (a, b, c) {
				return '__vmodel__.' + b + '.call(__vmodel__' + (/\S/.test(c) ? ',' + c : '') + ')';
			});
		}
		return body;
	}
	function createGetter(expr, type) {
		var arr = addScope(expr, type),
			body;
		if (!arr[1]) {
			body = arr[0];
		} else {
			body = arr[1].replace(/__value__\)$/, arr[0] + ')');
		}
		try {
			return new Function('__vmodel__', 'return ' + body + ';');
			/* istanbul ignore next */
		} catch (e) {
			avalon.log('parse getter: [', expr, body, ']error');
			return avalon.noop;
		}
	}

	/**
	 * 生成表达式设值函数
	 * @param  {String}  expr
	 */
	function createSetter(expr, type) {
		var arr = addScope(expr, type);
		var body = 'try{ ' + arr[0] + ' = __value__}catch(e){avalon.log(e, "in on dir")}';
		try {
			return new Function('__vmodel__', '__value__', body + ';');
			/* istanbul ignore next */
		} catch (e) {
			avalon.log('parse setter: ', expr, ' error');
			return avalon.noop;
		}
	}

	var actionUUID = 1;
	//需要重构
	function Action(vm, options, callback) {
		for (var i in options) {
			if (protectedMenbers[i] !== 1) {
				this[i] = options[i];
			}
		}

		this.vm = vm;
		this.observers = [];
		this.callback = callback;
		this.uuid = ++actionUUID;
		this.ids = '';
		this.mapIDs = {}; //这个用于去重
		this.isAction = true;
		var expr = this.expr;
		// 缓存取值函数
		if (typeof this.getter !== 'function') {
			this.getter = createGetter(expr, this.type);
		}
		// 缓存设值函数（双向数据绑定）
		if (this.type === 'duplex') {
			this.setter = createSetter(expr, this.type);
		}
		// 缓存表达式旧值
		this.value = NaN;
		// 表达式初始值 & 提取依赖
		if (!this.node) {
			this.value = this.get();
		}
	}

	Action.prototype = {
		getValue: function getValue() {
			var scope = this.vm;
			try {
				return this.getter.call(scope, scope);
			} catch (e) {
				avalon.log(this.getter + ' exec error');
			}
		},
		setValue: function setValue(value) {
			var scope = this.vm;
			if (this.setter) {
				this.setter.call(scope, scope, value);
			}
		},


		// get --> getValue --> getter
		get: function get(fn) {
			var name = 'action track ' + this.type;

			if (this.deep) {
				avalon.deepCollect = true;
			}

			var value = collectDeps(this, this.getValue);
			if (this.deep && avalon.deepCollect) {
				avalon.deepCollect = false;
			}

			return value;
		},


		/**
		 * 在更新视图前保存原有的value
		 */
		beforeUpdate: function beforeUpdate() {
			return this.oldValue = getPlainObject(this.value);
		},
		update: function update(args, uuid) {
			var oldVal = this.beforeUpdate();
			var newVal = this.value = this.get();
			var callback = this.callback;
			if (callback && this.diff(newVal, oldVal, args)) {
				callback.call(this.vm, this.value, oldVal, this.expr);
			}
			this._isScheduled = false;
		},
		schedule: function schedule() {
			if (!this._isScheduled) {
				this._isScheduled = true;
				if (!avalon.uniqActions[this.uuid]) {
					avalon.uniqActions[this.uuid] = 1;
					avalon.pendingActions.push(this);
				}

				runActions(); //这里会还原_isScheduled

			}
		},
		removeDepends: function removeDepends() {
			var self = this;
			this.observers.forEach(function (depend) {
				avalon.Array.remove(depend.observers, self);
			});
		},


		/**
		 * 比较两个计算值是否,一致,在for, class等能复杂数据类型的指令中,它们会重写diff复法
		 */
		diff: function diff(a, b) {
			return a !== b;
		},


		/**
		 * 销毁指令
		 */
		dispose: function dispose() {
			this.value = null;
			this.removeDepends();
			if (this.beforeDispose) {
				this.beforeDispose();
			}
			for (var i in this) {
				delete this[i];
			}
		}
	};

	function getPlainObject(v) {
		if (v && typeof v === 'object') {
			if (v && v.$events) {
				return v.$model;
			} else if (Array.isArray(v)) {
				var ret = [];
				for (var i = 0, n = v.length; i < n; i++) {
					ret.push(getPlainObject(v[i]));
				}
				return ret;
			} else {
				var _ret = {};
				for (var _i3 in v) {
					_ret[_i3] = getPlainObject(v[_i3]);
				}
				return _ret;
			}
		} else {
			return v;
		}
	}

	var protectedMenbers = {
		vm: 1,
		callback: 1,

		observers: 1,
		oldValue: 1,
		value: 1,
		getValue: 1,
		setValue: 1,
		get: 1,

		removeDepends: 1,
		beforeUpdate: 1,
		update: 1,
		//diff
		//getter
		//setter
		//expr
		//vdom
		//type: "for"
		//name: "ms-for"
		//attrName: ":for"
		//param: "click"
		//beforeDispose
		dispose: 1
	};

	/**
	 *
	 与Computed等共享UUID
	 */
	var obid = 1;
	function Mutation(expr, value, vm) {
		//构造函数
		this.expr = expr;
		if (value) {
			var childVm = platform.createProxy(value, this);
			if (childVm) {
				value = childVm;
			}
		}
		this.value = value;
		this.vm = vm;
		try {
			vm.$mutations[expr] = this;
		} catch (ignoreIE) {}
		this.uuid = ++obid;
		this.updateVersion();
		this.mapIDs = {};
		this.observers = [];
	}

	Mutation.prototype = {
		get: function get() {
			if (avalon.trackingAction) {
				this.collect(); //被收集
				var childOb = this.value;
				if (childOb && childOb.$events) {
					if (Array.isArray(childOb)) {
						childOb.forEach(function (item) {
							if (item && item.$events) {
								item.$events.__dep__.collect();
							}
						});
					} else if (avalon.deepCollect) {
						for (var key in childOb) {
							if (childOb.hasOwnProperty(key)) {
								var collectIt = childOb[key];
							}
						}
					}
				}
			}
			return this.value;
		},
		collect: function collect() {
			avalon.track(name, '被收集');
			reportObserved(this);
		},
		updateVersion: function updateVersion() {
			this.version = Math.random() + Math.random();
		},
		notify: function notify() {
			transactionStart();
			propagateChanged(this);
			transactionEnd();
		},
		set: function set(newValue) {
			var oldValue = this.value;
			if (newValue !== oldValue) {
				if (avalon.isObject(newValue)) {
					var hash = oldValue && oldValue.$hashcode;
					var childVM = platform.createProxy(newValue, this);
					if (childVM) {
						if (hash) {
							childVM.$hashcode = hash;
						}
						newValue = childVM;
					}
				}
				this.value = newValue;
				this.updateVersion();
				this.notify();
			}
		}
	};

	function getBody(fn) {
		var entire = fn.toString();
		return entire.substring(entire.indexOf('{}') + 1, entire.lastIndexOf('}'));
	}
	//如果不存在三目,if,方法
	var instability = /(\?|if\b|\(.+\))/;

	function __create(o) {
		var __ = function __() {};
		__.prototype = o;
		return new __();
	}

	function __extends(child, parent) {
		if (typeof parent === 'function') {
			var proto = child.prototype = __create(parent.prototype);
			proto.constructor = child;
		}
	}
	var Computed = function (_super) {
		__extends(Computed, _super);

		function Computed(name, options, vm) {
			//构造函数
			_super.call(this, name, undefined, vm);
			delete options.get;
			delete options.set;

			avalon.mix(this, options);
			this.deps = {};
			this.type = 'computed';
			this.depsVersion = {};
			this.isComputed = true;
			this.trackAndCompute();
			if (!('isStable' in this)) {
				this.isStable = !instability.test(getBody(this.getter));
			}
		}
		var cp = Computed.prototype;
		cp.trackAndCompute = function () {
			if (this.isStable && this.depsCount > 0) {
				this.getValue();
			} else {
				collectDeps(this, this.getValue.bind(this));
			}
		};

		cp.getValue = function () {
			return this.value = this.getter.call(this.vm);
		};

		cp.schedule = function () {
			var observers = this.observers;
			var i = observers.length;
			while (i--) {
				var d = observers[i];
				if (d.schedule) {
					d.schedule();
				}
			}
		};

		cp.shouldCompute = function () {
			if (this.isStable) {
				//如果变动因子确定,那么只比较变动因子的版本
				var toComputed = false;
				for (var i in this.deps) {
					if (this.deps[i].version !== this.depsVersion[i]) {
						toComputed = true;
						this.depsVersion[i] = this.deps[i].version;
					}
				}
				return toComputed;
			}
			return true;
		};
		cp.set = function () {
			if (this.setter) {
				avalon.transaction(this.setter, this.vm, arguments);
			}
		};
		cp.get = function () {

			//当被设置了就不稳定,当它被访问了一次就是稳定
			this.collect();

			if (this.shouldCompute()) {
				this.trackAndCompute();
				// console.log('computed 2 分支')
				this.updateVersion();
				//  this.reportChanged()
			}

			//下面这一行好像没用
			return this.value;
		};
		return Computed;
	}(Mutation);

	/**
	 * 这里放置ViewModel模块的共用方法
	 * avalon.define: 全框架最重要的方法,生成用户VM
	 * IProxy, 基本用户数据产生的一个数据对象,基于$model与vmodel之间的形态
	 * modelFactory: 生成用户VM
	 * canHijack: 判定此属性是否该被劫持,加入数据监听与分发的的逻辑
	 * createProxy: listFactory与modelFactory的封装
	 * createAccessor: 实现数据监听与分发的重要对象
	 * itemFactory: ms-for循环中产生的代理VM的生成工厂
	 * fuseFactory: 两个ms-controller间产生的代理VM的生成工厂
	 */

	avalon.define = function (definition) {
		var $id = definition.$id;
		if (!$id) {
			avalon.error('vm.$id must be specified');
		}
		if (avalon.vmodels[$id]) {
			avalon.warn('error:[' + $id + '] had defined!');
		}
		var vm = platform.modelFactory(definition);
		return avalon.vmodels[$id] = vm;
	};

	/**
	 * 在未来的版本,avalon改用Proxy来创建VM,因此
	 */

	function IProxy(definition, dd) {
		avalon.mix(this, definition);
		avalon.mix(this, $$skipArray);
		this.$hashcode = avalon.makeHashCode('$');
		this.$id = this.$id || this.$hashcode;
		this.$events = {
			__dep__: dd || new Mutation(this.$id)
		};
		if (avalon.config.inProxyMode) {
			delete this.$mutations;
			this.$accessors = {};
			this.$computed = {};
			this.$track = '';
		} else {
			this.$accessors = {
				$model: modelAccessor
			};
		}
		if (dd === void 0) {
			this.$watch = platform.watchFactory(this.$events);
			this.$fire = platform.fireFactory(this.$events);
		} else {
			delete this.$watch;
			delete this.$fire;
		}
	}

	platform.modelFactory = function modelFactory(definition, dd) {
		var $computed = definition.$computed || {};
		delete definition.$computed;
		var core = new IProxy(definition, dd);
		var $accessors = core.$accessors;
		var keys = [];

		platform.hideProperty(core, '$mutations', {});

		for (var key in definition) {
			if (key in $$skipArray) continue;
			var val = definition[key];
			keys.push(key);
			if (canHijack(key, val)) {
				$accessors[key] = createAccessor(key, val);
			}
		}
		for (var _key in $computed) {
			if (_key in $$skipArray) continue;
			var val = $computed[_key];
			if (typeof val === 'function') {
				val = {
					get: val
				};
			}
			if (val && val.get) {
				val.getter = val.get;
				val.setter = val.set;
				avalon.Array.ensure(keys, _key);
				$accessors[_key] = createAccessor(_key, val, true);
			}
		}
		//将系统API以unenumerable形式加入vm,
		//添加用户的其他不可监听属性或方法
		//重写$track
		//并在IE6-8中增添加不存在的hasOwnPropert方法
		var vm = platform.createViewModel(core, $accessors, core);
		platform.afterCreate(vm, core, keys, !dd);
		return vm;
	};
	var $proxyItemBackdoorMap = {};

	function canHijack(key, val, $proxyItemBackdoor) {
		if (key in $$skipArray) return false;
		if (key.charAt(0) === '$') {
			if ($proxyItemBackdoor) {
				if (!$proxyItemBackdoorMap[key]) {
					$proxyItemBackdoorMap[key] = 1;
					avalon.warn('ms-for\u4E2D\u7684\u53D8\u91CF' + key + '\u4E0D\u518D\u5EFA\u8BAE\u4EE5$\u4E3A\u524D\u7F00');
				}
				return true;
			}
			return false;
		}
		if (val == null) {
			avalon.warn('定义vmodel时' + key + '的属性值不能为null undefine');
			return true;
		}
		if (/error|date|function|regexp/.test(avalon.type(val))) {
			return false;
		}
		return !(val && val.nodeName && val.nodeType);
	}

	function createProxy(target, dd) {
		if (target && target.$events) {
			return target;
		}
		var vm;
		if (Array.isArray(target)) {
			vm = platform.listFactory(target, false, dd);
		} else if (isObject(target)) {
			vm = platform.modelFactory(target, dd);
		}
		return vm;
	}

	platform.createProxy = createProxy;

	platform.itemFactory = function itemFactory(before, after) {
		var keyMap = before.$model;
		var core = new IProxy(keyMap);
		var state = avalon.shadowCopy(core.$accessors, before.$accessors); //防止互相污染
		var data = after.data;
		//core是包含系统属性的对象
		//keyMap是不包含系统属性的对象, keys
		for (var key in data) {
			var val = keyMap[key] = core[key] = data[key];
			state[key] = createAccessor(key, val);
		}
		var keys = Object.keys(keyMap);
		var vm = platform.createViewModel(core, state, core);
		platform.afterCreate(vm, core, keys);
		return vm;
	};

	function createAccessor(key, val, isComputed) {
		var mutation = null;
		var Accessor = isComputed ? Computed : Mutation;
		return {
			get: function Getter() {
				if (!mutation) {
					mutation = new Accessor(key, val, this);
				}
				return mutation.get();
			},
			set: function Setter(newValue) {
				if (!mutation) {
					mutation = new Accessor(key, val, this);
				}
				mutation.set(newValue);
			},
			enumerable: true,
			configurable: true
		};
	}

	platform.fuseFactory = function fuseFactory(before, after) {
		var keyMap = avalon.mix(before.$model, after.$model);
		var core = new IProxy(avalon.mix(keyMap, {
			$id: before.$id + after.$id
		}));
		var state = avalon.mix(core.$accessors, before.$accessors, after.$accessors); //防止互相污染

		var keys = Object.keys(keyMap);
		//将系统API以unenumerable形式加入vm,并在IE6-8中添加hasOwnPropert方法
		var vm = platform.createViewModel(core, state, core);
		platform.afterCreate(vm, core, keys, false);
		return vm;
	};

	function toJson(val) {
		var xtype = avalon.type(val);
		if (xtype === 'array') {
			var array = [];
			for (var i = 0; i < val.length; i++) {
				array[i] = toJson(val[i]);
			}
			return array;
		} else if (xtype === 'object') {
			if (typeof val.$track === 'string') {
				var obj = {};
				var arr = val.$track.match(/[^☥]+/g) || [];
				arr.forEach(function (i) {
					var value = val[i];
					obj[i] = value && value.$events ? toJson(value) : value;
				});
				return obj;
			}
		}
		return val;
	}

	var modelAccessor = {
		get: function get() {
			return toJson(this);
		},
		set: avalon.noop,
		enumerable: false,
		configurable: true
	};

	platform.toJson = toJson;
	platform.modelAccessor = modelAccessor;

	var _splice = ap.splice;
	var __array__ = {
		set: function set(index, val) {
			if (index >>> 0 === index && this[index] !== val) {
				if (index > this.length) {
					throw Error(index + 'set方法的第一个参数不能大于原数组长度');
				}
				this.splice(index, 1, val);
			}
		},
		toJSON: function toJSON() {
			//为了解决IE6-8的解决,通过此方法显式地求取数组的$model
			return this.$model = platform.toJson(this);
		},
		contains: function contains(el) {
			//判定是否包含
			return this.indexOf(el) !== -1;
		},
		ensure: function ensure(el) {
			if (!this.contains(el)) {
				//只有不存在才push
				this.push(el);
				return true;
			}
			return false;
		},
		pushArray: function pushArray(arr) {
			return this.push.apply(this, arr);
		},
		remove: function remove(el) {
			//移除第一个等于给定值的元素
			return this.removeAt(this.indexOf(el));
		},
		removeAt: function removeAt(index) {
			//移除指定索引上的元素
			if (index >>> 0 === index) {
				return this.splice(index, 1);
			}
			return [];
		},
		clear: function clear() {
			this.removeAll();
			return this;
		},
		removeAll: function removeAll(all) {
			//移除N个元素
			var size = this.length;
			var eliminate = Array.isArray(all) ? function (el) {
				return all.indexOf(el) !== -1;
			} : typeof all === 'function' ? all : false;

			if (eliminate) {
				for (var i = this.length - 1; i >= 0; i--) {
					if (eliminate(this[i], i)) {
						_splice.call(this, i, 1);
					}
				}
			} else {
				_splice.call(this, 0, this.length);
			}
			this.toJSON();
			this.$events.__dep__.notify();
		}
	};
	function hijackMethods(array) {
		for (var i in __array__) {
			platform.hideProperty(array, i, __array__[i]);
		}
	}
	var __method__ = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'];

	__method__.forEach(function (method) {
		var original = ap[method];
		__array__[method] = function () {
			// 继续尝试劫持数组元素的属性
			var core = this.$events;

			var args = platform.listFactory(arguments, true, core.__dep__);
			var result = original.apply(this, args);

			this.toJSON();
			core.__dep__.notify(method);
			return result;
		};
	});

	function listFactory(array, stop, dd) {
		if (!stop) {
			hijackMethods(array);
			if (modern) {
				Object.defineProperty(array, '$model', platform.modelAccessor);
			}
			platform.hideProperty(array, '$hashcode', avalon.makeHashCode('$'));
			platform.hideProperty(array, '$events', { __dep__: dd || new Mutation() });
		}
		var _dd = array.$events && array.$events.__dep__;
		for (var i = 0, n = array.length; i < n; i++) {
			var item = array[i];
			if (isObject(item)) {
				array[i] = platform.createProxy(item, _dd);
			}
		}
		return array;
	}

	platform.listFactory = listFactory;

	//如果浏览器不支持ecma262v5的Object.defineProperties或者存在BUG，比如IE8
	//标准浏览器使用__defineGetter__, __defineSetter__实现
	var canHideProperty = true;
	try {
		Object.defineProperty({}, '_', {
			value: 'x'
		});
		delete $$skipArray.$vbsetter;
		delete $$skipArray.$vbthis;
	} catch (e) {
		/* istanbul ignore next*/
		canHideProperty = false;
	}

	var protectedVB = { $vbthis: 1, $vbsetter: 1 };
	/* istanbul ignore next */
	function hideProperty(host, name, value) {
		if (canHideProperty) {
			Object.defineProperty(host, name, {
				value: value,
				writable: true,
				enumerable: false,
				configurable: true
			});
		} else if (!protectedVB[name]) {
			/* istanbul ignore next */
			host[name] = value;
		}
	}

	function watchFactory(core) {
		return function $watch(expr, callback, deep) {
			var w = new Action(core.__proxy__, {
				deep: deep,
				type: 'user',
				expr: expr
			}, callback);
			if (!core[expr]) {
				core[expr] = [w];
			} else {
				core[expr].push(w);
			}

			return function () {
				w.dispose();
				avalon.Array.remove(core[expr], w);
				if (core[expr].length === 0) {
					delete core[expr];
				}
			};
		};
	}

	function fireFactory(core) {
		return function $fire(expr, a) {
			var list = core[expr];
			if (Array.isArray(list)) {
				for (var i = 0, w; w = list[i++];) {
					w.callback.call(w.vm, a, w.value, w.expr);
				}
			}
		};
	}

	function wrapIt(str) {
		return '☥' + str + '☥';
	}

	function afterCreate(vm, core, keys, bindThis) {
		var ac = vm.$accessors;
		//隐藏系统属性
		for (var key in $$skipArray) {
			if (avalon.msie < 9 && core[key] === void 0) continue;
			hideProperty(vm, key, core[key]);
		}
		//为不可监听的属性或方法赋值
		for (var i = 0; i < keys.length; i++) {
			var _key2 = keys[i];
			if (!(_key2 in ac)) {
				var val = core[_key2];
				if (bindThis && typeof val === 'function') {
					vm[_key2] = val.bind(vm);
					vm[_key2]._orig = val;
					continue;
				}
				vm[_key2] = val;
			}
		}
		vm.$track = keys.join('☥');

		function hasOwnKey(key) {
			return wrapIt(vm.$track).indexOf(wrapIt(key)) > -1;
		}
		if (avalon.msie < 9) {
			vm.hasOwnProperty = hasOwnKey;
		}
		vm.$events.__proxy__ = vm;
	}

	platform.hideProperty = hideProperty;
	platform.fireFactory = fireFactory;
	platform.watchFactory = watchFactory;
	platform.afterCreate = afterCreate;

	var createViewModel = Object.defineProperties;
	var defineProperty;

	var timeBucket = new Date() - 0;
	/* istanbul ignore if*/
	if (!canHideProperty) {
		if ('__defineGetter__' in avalon) {
			defineProperty = function defineProperty(obj, prop, desc) {
				if ('value' in desc) {
					obj[prop] = desc.value;
				}
				if ('get' in desc) {
					obj.__defineGetter__(prop, desc.get);
				}
				if ('set' in desc) {
					obj.__defineSetter__(prop, desc.set);
				}
				return obj;
			};
			createViewModel = function createViewModel(obj, descs) {
				for (var prop in descs) {
					if (descs.hasOwnProperty(prop)) {
						defineProperty(obj, prop, descs[prop]);
					}
				}
				return obj;
			};
		}
		/* istanbul ignore if*/
		if (msie$1 < 9) {
			var VBClassPool = {};
			window.execScript([// jshint ignore:line
				'Function parseVB(code)', '\tExecuteGlobal(code)', 'End Function' //转换一段文本为VB代码
			].join('\n'), 'VBScript');

			var VBMediator = function VBMediator(instance, accessors, name, value) {
				// jshint ignore:line
				var accessor = accessors[name];
				if (arguments.length === 4) {
					accessor.set.call(instance, value);
				} else {
					return accessor.get.call(instance);
				}
			};
			createViewModel = function createViewModel(name, accessors, properties) {
				// jshint ignore:line
				var buffer = [];
				buffer.push('\tPrivate [$vbsetter]', '\tPublic  [$accessors]', '\tPublic Default Function [$vbthis](ac' + timeBucket + ', s' + timeBucket + ')', '\t\tSet  [$accessors] = ac' + timeBucket + ': set [$vbsetter] = s' + timeBucket, '\t\tSet  [$vbthis]    = Me', //链式调用
					'\tEnd Function');
				//添加普通属性,因为VBScript对象不能像JS那样随意增删属性，必须在这里预先定义好
				var uniq = {
					$vbthis: true,
					$vbsetter: true,
					$accessors: true
				};
				for (name in $$skipArray) {
					if (!uniq[name]) {
						buffer.push('\tPublic [' + name + ']');
						uniq[name] = true;
					}
				}
				//添加访问器属性
				for (name in accessors) {
					if (uniq[name]) {
						continue;
					}
					uniq[name] = true;
					buffer.push(
						//由于不知对方会传入什么,因此set, let都用上
						'\tPublic Property Let [' + name + '](val' + timeBucket + ')', //setter
						'\t\tCall [$vbsetter](Me, [$accessors], "' + name + '", val' + timeBucket + ')', '\tEnd Property', '\tPublic Property Set [' + name + '](val' + timeBucket + ')', //setter
						'\t\tCall [$vbsetter](Me, [$accessors], "' + name + '", val' + timeBucket + ')', '\tEnd Property', '\tPublic Property Get [' + name + ']', //getter
						'\tOn Error Resume Next', //必须优先使用set语句,否则它会误将数组当字符串返回
						'\t\tSet[' + name + '] = [$vbsetter](Me, [$accessors],"' + name + '")', '\tIf Err.Number <> 0 Then', '\t\t[' + name + '] = [$vbsetter](Me, [$accessors],"' + name + '")', '\tEnd If', '\tOn Error Goto 0', '\tEnd Property');
				}

				for (name in properties) {
					if (!uniq[name]) {
						uniq[name] = true;
						buffer.push('\tPublic [' + name + ']');
					}
				}

				buffer.push('\tPublic [hasOwnProperty]');
				buffer.push('End Class');
				var body = buffer.join('\r\n');
				var className = VBClassPool[body];
				if (!className) {
					className = avalon.makeHashCode('VBClass');
					window.parseVB('Class ' + className + body);
					window.parseVB(['Function ' + className + 'Factory(acc, vbm)', //创建实例并传入两个关键的参数
						'\tDim o', '\tSet o = (New ' + className + ')(acc, vbm)', '\tSet ' + className + 'Factory = o', 'End Function'].join('\r\n'));
					VBClassPool[body] = className;
				}
				var ret = window[className + 'Factory'](accessors, VBMediator); //得到其产品
				return ret; //得到其产品
			};
		}
	}

	platform.createViewModel = createViewModel;

	var impDir = avalon.directive('important', {
		priority: 1,
		getScope: function getScope(name, scope) {
			var v = avalon.vmodels[name];
			if (v) return v;
			throw 'error! no vmodel called ' + name;
		},
		update: function update(node, attrName, $id) {
			if (!avalon.inBrowser) return;
			var dom = avalon.vdom(node, 'toDOM');
			if (dom.nodeType === 1) {
				dom.removeAttribute(attrName);
				avalon(dom).removeClass('ms-controller');
			}
			var vm = avalon.vmodels[$id];
			if (vm) {
				vm.$element = dom;
				vm.$render = this;
				vm.$fire('onReady');
				delete vm.$events.onReady;
			}
		}
	});

	var impCb = impDir.update;

	avalon.directive('controller', {
		priority: 2,
		getScope: function getScope(name, scope) {
			var v = avalon.vmodels[name];
			if (v) {
				v.$render = this;
				if (scope && scope !== v) {
					return platform.fuseFactory(scope, v);
				}
				return v;
			}
			return scope;
		},
		update: impCb
	});

	avalon.directive('skip', {
		delay: true
	});

	var arrayWarn = {};
	var cssDir = avalon.directive('css', {
		diff: function diff(newVal, oldVal) {
			if (Object(newVal) === newVal) {
				newVal = platform.toJson(newVal); //安全的遍历VBscript
				if (Array.isArray(newVal)) {
					//转换成对象
					var b = {};
					newVal.forEach(function (el) {
						el && avalon.shadowCopy(b, el);
					});
					newVal = b;
					if (!arrayWarn[this.type]) {
						avalon.warn('ms-' + this.type + '指令的值不建议使用数组形式了！');
						arrayWarn[this.type] = 1;
					}
				}

				var hasChange = false;
				var patch = {};
				if (!oldVal) {
					//如果一开始为空
					patch = newVal;
					hasChange = true;
				} else {
					if (this.deep) {
						var deep = typeof this.deep === 'number' ? this.deep : 6;
						for (var i in newVal) {
							//diff差异点
							if (!deepEquals(newVal[i], oldVal[i], 4)) {
								this.value = newVal;
								return true;
							}
							patch[i] = newVal[i];
						}
					} else {
						for (var _i4 in newVal) {
							//diff差异点
							if (newVal[_i4] !== oldVal[_i4]) {
								hasChange = true;
							}
							patch[_i4] = newVal[_i4];
						}
					}

					for (var _i5 in oldVal) {
						if (!(_i5 in patch)) {
							hasChange = true;
							patch[_i5] = '';
						}
					}
				}
				if (hasChange) {
					this.value = patch;
					return true;
				}
			}
			return false;
		},
		update: function update(vdom, value) {

			var dom = vdom.dom;
			if (dom && dom.nodeType === 1) {
				var wrap = avalon(dom);
				for (var name in value) {
					wrap.css(name, value[name]);
				}
			}
		}
	});

	var cssDiff = cssDir.diff;

	function getEnumerableKeys(obj) {
		var res = [];
		for (var key in obj) {
			res.push(key);
		}return res;
	}

	function deepEquals(a, b, level) {
		if (level === 0) return a === b;
		if (a === null && b === null) return true;
		if (a === undefined && b === undefined) return true;
		var aIsArray = Array.isArray(a);
		if (aIsArray !== Array.isArray(b)) {
			return false;
		}
		if (aIsArray) {
			return equalArray(a, b, level);
		} else if (typeof a === "object" && typeof b === "object") {
			return equalObject(a, b, level);
		}
		return a === b;
	}

	function equalArray(a, b, level) {
		if (a.length !== b.length) {
			return false;
		}
		for (var i = a.length - 1; i >= 0; i--) {
			try {
				if (!deepEquals(a[i], b[i], level - 1)) {
					return false;
				}
			} catch (noThisPropError) {
				return false;
			}
		}
		return true;
	}

	function equalObject(a, b, level) {
		if (a === null || b === null) return false;
		if (getEnumerableKeys(a).length !== getEnumerableKeys(b).length) return false;
		for (var prop in a) {
			if (!(prop in b)) return false;
			try {
				if (!deepEquals(a[prop], b[prop], level - 1)) {
					return false;
				}
			} catch (noThisPropError) {
				return false;
			}
		}
		return true;
	}

	/**
	 * ------------------------------------------------------------
	 * 检测浏览器对CSS动画的支持与API名
	 * ------------------------------------------------------------
	 */

	var checker = {
		TransitionEvent: 'transitionend',
		WebKitTransitionEvent: 'webkitTransitionEnd',
		OTransitionEvent: 'oTransitionEnd',
		otransitionEvent: 'otransitionEnd'
	};
	var css3 = void 0;
	var tran = void 0;
	var ani = void 0;
	var name$2 = void 0;
	var animationEndEvent = void 0;
	var transitionEndEvent = void 0;
	var transition = false;
	var animation = false;
	//有的浏览器同时支持私有实现与标准写法，比如webkit支持前两种，Opera支持1、3、4
	for (name$2 in checker) {
		if (window$1[name$2]) {
			tran = checker[name$2];
			break;
		}
		/* istanbul ignore next */
		try {
			var a = document.createEvent(name$2);
			tran = checker[name$2];
			break;
		} catch (e) {}
	}
	if (typeof tran === 'string') {
		transition = css3 = true;
		transitionEndEvent = tran;
	}

	//animationend有两个可用形态
	//IE10+, Firefox 16+ & Opera 12.1+: animationend
	//Chrome/Safari: webkitAnimationEnd
	//http://blogs.msdn.com/b/davrous/archive/2011/12/06/introduction-to-css3-animat ions.aspx
	//IE10也可以使用MSAnimationEnd监听，但是回调里的事件 type依然为animationend
	//  el.addEventListener('MSAnimationEnd', function(e) {
	//     alert(e.type)// animationend！！！
	// })
	checker = {
		'AnimationEvent': 'animationend',
		'WebKitAnimationEvent': 'webkitAnimationEnd'
	};
	for (name$2 in checker) {
		if (window$1[name$2]) {
			ani = checker[name$2];
			break;
		}
	}
	if (typeof ani === 'string') {
		animation = css3 = true;
		animationEndEvent = ani;
	}

	var effectDir = avalon.directive('effect', {
		priority: 5,
		diff: function diff(effect) {
			var vdom = this.node;
			if (typeof effect === 'string') {
				this.value = effect = {
					is: effect
				};
				avalon.warn('ms-effect的指令值不再支持字符串,必须是一个对象');
			}
			this.value = vdom.effect = effect;
			var ok = cssDiff.call(this, effect, this.oldValue);
			var me = this;
			if (ok) {
				setTimeout(function () {
					vdom.animating = true;
					effectDir.update.call(me, vdom, vdom.effect);
				});
				vdom.animating = false;
				return true;
			}
			return false;
		},

		update: function update(vdom, change, opts) {
			var dom = vdom.dom;
			if (dom && dom.nodeType === 1) {
				//要求配置对象必须指定is属性，action必须是布尔或enter,leave,move
				var option = change || opts;
				var is = option.is;

				var globalOption = avalon.effects[is];
				if (!globalOption) {
					//如果没有定义特效
					avalon.warn(is + ' effect is undefined');
					return;
				}
				var finalOption = {};
				var action = actionMaps[option.action];
				if (typeof Effect.prototype[action] !== 'function') {
					avalon.warn('action is undefined');
					return;
				}
				//必须预定义特效

				var effect = new avalon.Effect(dom);
				avalon.mix(finalOption, globalOption, option, { action: action });

				if (finalOption.queue) {
					animationQueue.push(function () {
						effect[action](finalOption);
					});
					callNextAnimation();
				} else {

					effect[action](finalOption);
				}
				return true;
			}
		}
	});

	var move = 'move';
	var leave = 'leave';
	var enter = 'enter';
	var actionMaps = {
		'true': enter,
		'false': leave,
		enter: enter,
		leave: leave,
		move: move,
		'undefined': enter
	};

	var animationQueue = [];
	function callNextAnimation() {
		var fn = animationQueue[0];
		if (fn) {
			fn();
		}
	}

	avalon.effects = {};
	avalon.effect = function (name, opts) {
		var definition = avalon.effects[name] = opts || {};
		if (css3 && definition.css !== false) {
			patchObject(definition, 'enterClass', name + '-enter');
			patchObject(definition, 'enterActiveClass', definition.enterClass + '-active');
			patchObject(definition, 'leaveClass', name + '-leave');
			patchObject(definition, 'leaveActiveClass', definition.leaveClass + '-active');
		}
		return definition;
	};

	function patchObject(obj, name, value) {
		if (!obj[name]) {
			obj[name] = value;
		}
	}

	var Effect = function Effect(dom) {
		this.dom = dom;
	};

	avalon.Effect = Effect;

	Effect.prototype = {
		enter: createAction('Enter'),
		leave: createAction('Leave'),
		move: createAction('Move')
	};

	function execHooks(options, name, el) {
		var fns = [].concat(options[name]);
		for (var i = 0, fn; fn = fns[i++];) {
			if (typeof fn === 'function') {
				fn(el);
			}
		}
	}
	var staggerCache = new Cache(128);

	function createAction(action) {
		var lower = action.toLowerCase();
		return function (option) {
			var dom = this.dom;
			var elem = avalon(dom);
			//处理与ms-for指令相关的stagger
			//========BEGIN=====
			var staggerTime = isFinite(option.stagger) ? option.stagger * 1000 : 0;
			if (staggerTime) {
				if (option.staggerKey) {
					var stagger = staggerCache.get(option.staggerKey) || staggerCache.put(option.staggerKey, {
						count: 0,
						items: 0
					});
					stagger.count++;
					stagger.items++;
				}
			}
			var staggerIndex = stagger && stagger.count || 0;
			//=======END==========
			var stopAnimationID;
			var animationDone = function animationDone(e) {
				var isOk = e !== false;
				if (--dom.__ms_effect_ === 0) {
					avalon.unbind(dom, transitionEndEvent);
					avalon.unbind(dom, animationEndEvent);
				}
				clearTimeout(stopAnimationID);
				var dirWord = isOk ? 'Done' : 'Abort';
				execHooks(option, 'on' + action + dirWord, dom);
				if (stagger) {
					if (--stagger.items === 0) {
						stagger.count = 0;
					}
				}
				if (option.queue) {
					animationQueue.shift();
					callNextAnimation();
				}
			};
			//执行开始前的钩子
			execHooks(option, 'onBefore' + action, dom);

			if (option[lower]) {
				//使用JS方式执行动画
				option[lower](dom, function (ok) {
					animationDone(ok !== false);
				});
			} else if (css3) {
				//使用CSS3方式执行动画
				elem.addClass(option[lower + 'Class']);
				elem.removeClass(getNeedRemoved(option, lower));

				if (!dom.__ms_effect_) {
					//绑定动画结束事件
					elem.bind(transitionEndEvent, animationDone);
					elem.bind(animationEndEvent, animationDone);
					dom.__ms_effect_ = 1;
				} else {
					dom.__ms_effect_++;
				}
				setTimeout(function () {
					//用xxx-active代替xxx类名的方式 触发CSS3动画
					var time = avalon.root.offsetWidth === NaN;
					elem.addClass(option[lower + 'ActiveClass']);
					//计算动画时长
					time = getAnimationTime(dom);
					if (!time === 0) {
						//立即结束动画
						animationDone(false);
					} else if (!staggerTime) {
						//如果动画超出时长还没有调用结束事件,这可能是元素被移除了
						//如果强制结束动画
						stopAnimationID = setTimeout(function () {
							animationDone(false);
						}, time + 32);
					}
				}, 17 + staggerTime * staggerIndex); // = 1000/60
			}
		};
	}

	avalon.applyEffect = function (dom, vdom, opts) {
		var cb = opts.cb;
		var curEffect = vdom.effect;
		if (curEffect && dom && dom.nodeType === 1) {
			var hook = opts.hook;
			var old = curEffect[hook];
			if (cb) {
				if (Array.isArray(old)) {
					old.push(cb);
				} else if (old) {
					curEffect[hook] = [old, cb];
				} else {
					curEffect[hook] = [cb];
				}
			}
			getAction(opts);
			avalon.directives.effect.update(vdom, curEffect, avalon.shadowCopy({}, opts));
		} else if (cb) {
			cb(dom);
		}
	};
	/**
	 * 获取方向
	 */
	function getAction(opts) {
		if (!opts.action) {
			return opts.action = opts.hook.replace(/^on/, '').replace(/Done$/, '').toLowerCase();
		}
	}
	/**
	 * 需要移除的类名
	 */
	function getNeedRemoved(options, name) {
		var name = name === 'leave' ? 'enter' : 'leave';
		return Array(name + 'Class', name + 'ActiveClass').map(function (cls) {
			return options[cls];
		}).join(' ');
	}
	/**
	 * 计算动画长度
	 */
	var transitionDuration = avalon.cssName('transition-duration');
	var animationDuration = avalon.cssName('animation-duration');
	var rsecond = /\d+s$/;
	function toMillisecond(str) {
		var ratio = rsecond.test(str) ? 1000 : 1;
		return parseFloat(str) * ratio;
	}

	function getAnimationTime(dom) {
		var computedStyles = window$1.getComputedStyle(dom, null);
		var tranDuration = computedStyles[transitionDuration];
		var animDuration = computedStyles[animationDuration];
		return toMillisecond(tranDuration) || toMillisecond(animDuration);
	}
	/**
	 *
	 <!DOCTYPE html>
	 <html>
	 <head>
	 <meta charset="UTF-8">
	 <meta name="viewport" content="width=device-width, initial-scale=1.0">
	 <script src="dist/avalon.js"></script>
	 <script>
	 avalon.effect('animate')
	 var vm = avalon.define({
                    $id: 'ani',
                    a: true
                })
	 </script>
	 <style>
	 .animate-enter, .animate-leave{
                    width:100px;
                    height:100px;
                    background: #29b6f6;
                    transition:all 2s;
                    -moz-transition: all 2s;
                    -webkit-transition: all 2s;
                    -o-transition:all 2s;
                }
	 .animate-enter-active, .animate-leave{
                    width:300px;
                    height:300px;
                }
	 .animate-leave-active{
                    width:100px;
                    height:100px;
                }
	 </style>
	 </head>
	 <body>
	 <div :controller='ani' >
	 <p><input type='button' value='click' :click='@a =!@a'></p>
	 <div :effect="{is:'animate',action:@a}"></div>
	 </div>
	 </body>
	 </html>
	 *
	 */

	var none = 'none';
	function parseDisplay(elem, val) {
		//用于取得此类标签的默认display值
		var doc = elem.ownerDocument;
		var nodeName = elem.nodeName;
		var key = '_' + nodeName;
		if (!parseDisplay[key]) {
			var temp = doc.body.appendChild(doc.createElement(nodeName));
			val = avalon.css(temp, 'display');
			doc.body.removeChild(temp);
			if (val === none) {
				val = 'block';
			}
			parseDisplay[key] = val;
		}
		return parseDisplay[key];
	}

	avalon.parseDisplay = parseDisplay;
	avalon.directive('visible', {
		diff: function diff(newVal, oldVal) {
			var n = !!newVal;
			if (oldVal === void 0 || n !== oldVal) {
				this.value = n;
				return true;
			}
		},
		ready: true,
		update: function update(vdom, show) {
			var dom = vdom.dom;
			if (dom && dom.nodeType === 1) {
				var display = dom.style.display;
				var value;
				if (show) {
					if (display === none) {
						value = vdom.displayValue;
						if (!value) {
							dom.style.display = '';
							if (dom.style.cssText === '') {
								dom.removeAttribute('style');
							}
						}
					}
					if (dom.style.display === '' && avalon(dom).css('display') === none &&
						// fix firefox BUG,必须挂到页面上
						avalon.contains(dom.ownerDocument, dom)) {
						value = parseDisplay(dom);
					}
				} else {

					if (display !== none) {
						value = none;
						vdom.displayValue = display;
					}
				}
				var cb = function cb() {
					if (value !== void 0) {
						dom.style.display = value;
					}
				};

				avalon.applyEffect(dom, vdom, {
					hook: show ? 'onEnterDone' : 'onLeaveDone',
					cb: cb
				});
			}
		}
	});

	avalon.directive('text', {
		delay: true,
		init: function init() {

			var node = this.node;
			if (node.isVoidTag) {
				avalon.error('自闭合元素不能使用ms-text');
			}
			var child = { nodeName: '#text', nodeValue: this.getValue() };
			node.children.splice(0, node.children.length, child);
			if (inBrowser) {
				avalon.clearHTML(node.dom);
				node.dom.appendChild(avalon.vdom(child, 'toDOM'));
			}
			this.node = child;
			var type = 'expr';
			this.type = this.name = type;
			var directive$$1 = avalon.directives[type];
			var me = this;
			this.callback = function (value) {
				directive$$1.update.call(me, me.node, value);
			};
		}
	});

	avalon.directive('expr', {
		update: function update(vdom, value) {
			value = value == null || value === '' ? '\u200B' : value;
			vdom.nodeValue = value;
			//https://github.com/RubyLouvre/avalon/issues/1834
			if (vdom.dom) vdom.dom.data = value;
		}
	});

	avalon.directive('attr', {
		diff: cssDiff,
		update: function update(vdom, value) {
			var props = vdom.props;
			for (var i in value) {
				if (!!value[i] === false) {
					delete props[i];
				} else {
					props[i] = value[i];
				}
			}
			var dom = vdom.dom;
			if (dom && dom.nodeType === 1) {
				updateAttrs(dom, value);
			}
		}
	});

	avalon.directive('html', {

		update: function update(vdom, value) {
			this.beforeDispose();

			this.innerRender = avalon.scan('<div class="ms-html-container">' + value + '</div>', this.vm, function () {
				var oldRoot = this.root;
				if (vdom.children) vdom.children.length = 0;
				vdom.children = oldRoot.children;
				this.root = vdom;
				if (vdom.dom) avalon.clearHTML(vdom.dom);
			});
		},
		beforeDispose: function beforeDispose() {
			if (this.innerRender) {
				this.innerRender.dispose();
			}
		},
		delay: true
	});

	avalon.directive('if', {
		delay: true,
		priority: 5,
		init: function init() {
			this.placeholder = createAnchor('if');
			var props = this.node.props;
			delete props['ms-if'];
			delete props[':if'];
			this.fragment = avalon.vdom(this.node, 'toHTML');
		},
		diff: function diff(newVal, oldVal) {
			var n = !!newVal;
			if (oldVal === void 0 || n !== oldVal) {
				this.value = n;
				return true;
			}
		},
		update: function update(vdom, value) {
			if (this.isShow === void 0 && value) {
				continueScan(this, vdom);
				return;
			}
			this.isShow = value;
			var placeholder = this.placeholder;

			if (value) {
				var p = placeholder.parentNode;
				continueScan(this, vdom);
				p && p.replaceChild(vdom.dom, placeholder);
			} else {
				//移除DOM
				this.beforeDispose();
				vdom.nodeValue = 'if';
				vdom.nodeName = '#comment';
				delete vdom.children;
				var dom = vdom.dom;
				var p = dom && dom.parentNode;
				vdom.dom = placeholder;
				if (p) {
					p.replaceChild(placeholder, dom);
				}
			}
		},
		beforeDispose: function beforeDispose() {
			if (this.innerRender) {
				this.innerRender.dispose();
			}
		}
	});

	function continueScan(instance, vdom) {
		var innerRender = instance.innerRender = avalon.scan(instance.fragment, instance.vm);
		avalon.shadowCopy(vdom, innerRender.root);
		delete vdom.nodeValue;
	}

	avalon.directive('on', {
		beforeInit: function beforeInit() {
			this.getter = avalon.noop;
		},
		init: function init() {
			var vdom = this.node;
			var underline = this.name.replace('ms-on-', 'e').replace('-', '_');
			var uuid = underline + '_' + this.expr.replace(/\s/g, '').replace(/[^$a-z]/ig, function (e) {
				return e.charCodeAt(0);
			});
			var fn = avalon.eventListeners[uuid];
			if (!fn) {
				var arr = addScope(this.expr);
				var body = arr[0],
					filters = arr[1];
				body = makeHandle(body);

				if (filters) {
					filters = filters.replace(/__value__/g, '$event');
					filters += '\nif($event.$return){\n\treturn;\n}';
				}
				var ret = ['try{', '\tvar __vmodel__ = this;', '\t' + filters, '\treturn ' + body, '}catch(e){avalon.log(e, "in on dir")}'].filter(function (el) {
					return (/\S/.test(el)
					);
				});
				fn = new Function('$event', ret.join('\n'));
				fn.uuid = uuid;
				avalon.eventListeners[uuid] = fn;
			}

			var dom = avalon.vdom(vdom, 'toDOM');
			dom._ms_context_ = this.vm;

			this.eventType = this.param.replace(/\-(\d)$/, '');
			delete this.param;
			avalon(dom).bind(this.eventType, fn);
		},

		beforeDispose: function beforeDispose() {
			avalon(this.node.dom).unbind(this.eventType);
		}
	});

	function lookupOption(vdom, values) {
		vdom.children && vdom.children.forEach(function (el) {
			if (el.nodeName === 'option') {
				setOption(el, values);
			} else {
				lookupOption(el, values);
			}
		});
	}

	function setOption(vdom, values) {
		var props = vdom.props;
		if (!('disabled' in props)) {
			var value = getOptionValue(vdom, props);
			value = String(value || '').trim();
			if (typeof values === 'string') {
				props.selected = value === values;
			} else {
				props.selected = values.indexOf(value) !== -1;
			}

			if (vdom.dom) {
				vdom.dom.selected = props.selected;
				var v = vdom.dom.selected; //必须加上这个,防止移出节点selected失效
			}
		}
	}

	function getOptionValue(vdom, props) {
		if (props && 'value' in props) {
			return props.value + '';
		}
		var arr = [];
		vdom.children.forEach(function (el) {
			if (el.nodeName === '#text') {
				arr.push(el.nodeValue);
			} else if (el.nodeName === '#document-fragment') {
				arr.push(getOptionValue(el));
			}
		});
		return arr.join('');
	}

	function getSelectedValue(vdom, arr) {
		vdom.children.forEach(function (el) {
			if (el.nodeName === 'option') {
				if (el.props.selected === true) arr.push(getOptionValue(el, el.props));
			} else if (el.children) {
				getSelectedValue(el, arr);
			}
		});
		return arr;
	}

	var updateDataActions = {
		input: function input(prop) {
			//处理单个value值处理
			var field = this;
			prop = prop || 'value';
			var dom = field.dom;
			var rawValue = dom[prop];
			var parsedValue = field.parseValue(rawValue);

			//有时候parse后一致,vm不会改变,但input里面的值
			field.value = rawValue;
			field.setValue(parsedValue);
			duplexCb(field);
			var pos = field.pos;
			/* istanbul ignore if */
			if (dom.caret) {
				field.setCaret(dom, pos);
			}
			//vm.aaa = '1234567890'
			//处理 <input ms-duplex='@aaa|limitBy(8)'/>{{@aaa}} 这种格式化同步不一致的情况
		},
		radio: function radio() {
			var field = this;
			if (field.isChecked) {
				var val = !field.value;
				field.setValue(val);
				duplexCb(field);
			} else {
				updateDataActions.input.call(field);
				field.value = NaN;
			}
		},
		checkbox: function checkbox() {
			var field = this;
			var array = field.value;
			if (!Array.isArray(array)) {
				avalon.warn('ms-duplex应用于checkbox上要对应一个数组');
				array = [array];
			}
			var method = field.dom.checked ? 'ensure' : 'remove';
			if (array[method]) {
				var val = field.parseValue(field.dom.value);
				array[method](val);
				duplexCb(field);
			}
			this.__test__ = array;
		},
		select: function select() {
			var field = this;
			var val = avalon(field.dom).val(); //字符串或字符串数组
			if (val + '' !== this.value + '') {
				if (Array.isArray(val)) {
					//转换布尔数组或其他
					val = val.map(function (v) {
						return field.parseValue(v);
					});
				} else {
					val = field.parseValue(val);
				}
				field.setValue(val);
				duplexCb(field);
			}
		},
		contenteditable: function contenteditable() {
			updateDataActions.input.call(this, 'innerHTML');
		}
	};

	function duplexCb(field) {
		if (field.userCb) {
			field.userCb.call(field.vm, {
				type: 'changed',
				target: field.dom
			});
		}
	}

	function updateDataHandle(event) {
		var elem = this;
		var field = elem._ms_duplex_;
		if (elem.composing) {
			//防止onpropertychange引发爆栈
			return;
		}
		if (elem.value === field.value) {
			return;
		}
		/* istanbul ignore if*/
		if (elem.caret) {
			try {
				var pos = field.getCaret(elem);
				field.pos = pos;
			} catch (e) {}
		}
		/* istanbul ignore if*/
		if (field.debounceTime > 4) {
			var timestamp = new Date();
			var left = timestamp - field.time || 0;
			field.time = timestamp;
			/* istanbul ignore if*/
			if (left >= field.debounceTime) {
				updateDataActions[field.dtype].call(field);
				/* istanbul ignore else*/
			} else {
				clearTimeout(field.debounceID);
				field.debounceID = setTimeout(function () {
					updateDataActions[field.dtype].call(field);
				}, left);
			}
		} else if (field.isChanged) {
			setTimeout(function () {
				//https://github.com/RubyLouvre/avalon/issues/1908
				updateDataActions[field.dtype].call(field);
			}, 4);
		} else {
			updateDataActions[field.dtype].call(field);
		}
	}

	var rchangeFilter = /\|\s*change\b/;
	var rdebounceFilter = /\|\s*debounce(?:\(([^)]+)\))?/;
	function duplexBeforeInit() {
		var expr = this.expr;
		if (rchangeFilter.test(expr)) {
			this.isChanged = true;
			expr = expr.replace(rchangeFilter, '');
		}
		var match = expr.match(rdebounceFilter);
		if (match) {
			expr = expr.replace(rdebounceFilter, '');
			if (!this.isChanged) {
				this.debounceTime = parseInt(match[1], 10) || 300;
			}
		}
		this.expr = expr;
	}
	function duplexInit() {
		var expr = this.expr;
		var node = this.node;
		var etype = node.props.type;
		this.parseValue = parseValue;
		//处理数据转换器
		var parsers = this.param,
			dtype;
		var isChecked = false;
		parsers = parsers ? parsers.split('-').map(function (a) {
			if (a === 'checked') {
				isChecked = true;
			}
			return a;
		}) : [];
		node.duplex = this;
		if (rcheckedType.test(etype) && isChecked) {
			//如果是radio, checkbox,判定用户使用了checked格式函数没有
			parsers = [];
			dtype = 'radio';
			this.isChecked = isChecked;
		}
		this.parsers = parsers;
		if (!/input|textarea|select/.test(node.nodeName)) {
			if ('contenteditable' in node.props) {
				dtype = 'contenteditable';
			}
		} else if (!dtype) {
			dtype = node.nodeName === 'select' ? 'select' : etype === 'checkbox' ? 'checkbox' : etype === 'radio' ? 'radio' : 'input';
		}
		this.dtype = dtype;

		//判定是否使用了 change debounce 过滤器
		// this.isChecked = /boolean/.test(parsers)
		if (dtype !== 'input' && dtype !== 'contenteditable') {
			delete this.isChanged;
			delete this.debounceTime;
		} else if (!this.isChecked) {
			this.isString = true;
		}

		var cb = node.props['data-duplex-changed'];
		if (cb) {
			var arr = addScope(cb, 'xx');
			var body = makeHandle(arr[0]);
			this.userCb = new Function('$event', 'var __vmodel__ = this\nreturn ' + body);
		}
	}
	function duplexDiff(newVal, oldVal) {
		if (Array.isArray(newVal)) {
			if (newVal + '' !== this.compareVal) {
				this.compareVal = newVal + '';
				return true;
			}
		} else {
			newVal = this.parseValue(newVal);
			if (!this.isChecked) {
				this.value = newVal += '';
			}
			if (newVal !== this.compareVal) {
				this.compareVal = newVal;
				return true;
			}
		}
	}

	function duplexBind(vdom, addEvent) {
		var dom = vdom.dom;
		this.dom = dom;
		this.vdom = vdom;
		this.duplexCb = updateDataHandle;
		dom._ms_duplex_ = this;
		//绑定事件
		addEvent(dom, this);
	}

	var valueHijack = true;
	try {
		//#272 IE9-IE11, firefox
		var setters = {};
		var aproto = HTMLInputElement.prototype;
		var bproto = HTMLTextAreaElement.prototype;
		var newSetter = function newSetter(value) {
			// jshint ignore:line
			setters[this.tagName].call(this, value);
			var data = this._ms_duplex_;
			if (!this.caret && data && data.isString) {
				data.duplexCb.call(this, { type: 'setter' });
			}
		};
		var inputProto = HTMLInputElement.prototype;
		Object.getOwnPropertyNames(inputProto); //故意引发IE6-8等浏览器报错
		setters['INPUT'] = Object.getOwnPropertyDescriptor(aproto, 'value').set;

		Object.defineProperty(aproto, 'value', {
			set: newSetter
		});
		setters['TEXTAREA'] = Object.getOwnPropertyDescriptor(bproto, 'value').set;
		Object.defineProperty(bproto, 'value', {
			set: newSetter
		});
		valueHijack = false;
	} catch (e) {
		//在chrome 43中 ms-duplex终于不需要使用定时器实现双向绑定了
		// http://updates.html5rocks.com/2015/04/DOM-attributes-now-on-the-prototype
		// https://docs.google.com/document/d/1jwA8mtClwxI-QJuHT7872Z0pxpZz8PBkf2bGAbsUtqs/edit?pli=1
	}

	function parseValue(val) {
		for (var i = 0, k; k = this.parsers[i++];) {
			var fn = avalon.parsers[k];
			if (fn) {
				val = fn.call(this, val);
			}
		}
		return val;
	}

	var updateView = {
		input: function input() {
			//处理单个value值处理
			var vdom = this.node;
			var value = this.value + '';
			vdom.dom.value = vdom.props.value = value;
		},
		updateChecked: function updateChecked(vdom, checked) {
			if (vdom.dom) {
				vdom.dom.defaultChecked = vdom.dom.checked = checked;
			}
		},
		radio: function radio() {
			//处理单个checked属性
			var node = this.node;
			var nodeValue = node.props.value;
			var checked;
			if (this.isChecked) {
				checked = !!this.value;
			} else {
				checked = this.value + '' === nodeValue;
			}
			node.props.checked = checked;
			updateView.updateChecked(node, checked);
		},
		checkbox: function checkbox() {
			//处理多个checked属性
			var node = this.node;
			var props = node.props;
			var value = props.value + '';
			var values = [].concat(this.value);
			var checked = values.some(function (el) {
				return el + '' === value;
			});

			props.defaultChecked = props.checked = checked;
			updateView.updateChecked(node, checked);
		},
		select: function select() {
			//处理子级的selected属性
			var a = Array.isArray(this.value) ? this.value.map(String) : this.value + '';
			lookupOption(this.node, a);
		},
		contenteditable: function contenteditable() {
			//处理单个innerHTML

			var vnodes = fromString(this.value);
			var fragment = createFragment();
			for (var i = 0, el; el = vnodes[i++];) {
				var child = avalon.vdom(el, 'toDOM');
				fragment.appendChild(child);
			}
			avalon.clearHTML(this.dom).appendChild(fragment);
			var list = this.node.children;
			list.length = 0;
			Array.prototype.push.apply(list, vnodes);

			this.duplexCb.call(this.dom);
		}
	};

	var rforAs = /\s+as\s+([$\w]+)/;
	var rident = /^[$a-zA-Z_][$a-zA-Z0-9_]*$/;
	var rinvalid = /^(null|undefined|NaN|window|this|\$index|\$id)$/;
	var rargs = /[$\w_]+/g;
	avalon.directive('for', {
		delay: true,
		priority: 3,
		beforeInit: function beforeInit() {
			var str = this.expr,
				asName;
			str = str.replace(rforAs, function (a, b) {
				/* istanbul ignore if */
				if (!rident.test(b) || rinvalid.test(b)) {
					avalon.error('alias ' + b + ' is invalid --- must be a valid JS identifier which is not a reserved name.');
				} else {
					asName = b;
				}
				return '';
			});

			var arr = str.split(' in ');
			var kv = arr[0].match(rargs);
			if (kv.length === 1) {
				//确保avalon._each的回调有三个参数
				kv.unshift('$key');
			}
			this.expr = arr[1];
			this.keyName = kv[0];
			this.valName = kv[1];
			this.signature = avalon.makeHashCode('for');
			if (asName) {
				this.asName = asName;
			}

			delete this.param;
		},
		init: function init() {
			var cb = this.userCb;
			if (typeof cb === 'string' && cb) {
				var arr = addScope(cb, 'for');
				var body = makeHandle(arr[0]);
				this.userCb = new Function('$event', 'var __vmodel__ = this\nreturn ' + body);
			}
			this.node.forDir = this; //暴露给component/index.js中的resetParentChildren方法使用
			this.fragment = ['<div>', this.fragment, '<!--', this.signature, '--></div>'].join('');
			this.cache = {};
		},
		diff: function diff(newVal, oldVal) {
			/* istanbul ignore if */
			if (this.updating) {
				return;
			}
			this.updating = true;
			var traceIds = createFragments(this, newVal);

			if (this.oldTrackIds === void 0) return true;

			if (this.oldTrackIds !== traceIds) {
				this.oldTrackIds = traceIds;
				return true;
			}
		},
		update: function update() {

			if (!this.preFragments) {
				this.fragments = this.fragments || [];
				mountList(this);
			} else {
				diffList(this);
				updateList(this);
			}

			if (this.userCb) {
				var me = this;
				setTimeout(function () {
					me.userCb.call(me.vm, {
						type: 'rendered',
						target: me.begin.dom,
						signature: me.signature
					});
				}, 0);
			}
			delete this.updating;
		},
		beforeDispose: function beforeDispose() {
			this.fragments.forEach(function (el) {
				el.dispose();
			});
		}
	});

	function getTraceKey(item) {
		var type = typeof item;
		return item && type === 'object' ? item.$hashcode : type + ':' + item;
	}

	//创建一组fragment的虚拟DOM
	function createFragments(instance, obj) {
		if (isObject(obj)) {
			var array = Array.isArray(obj);
			var ids = [];
			var fragments = [],
				i = 0;

			instance.isArray = array;
			if (instance.fragments) {
				instance.preFragments = instance.fragments;
				avalon.each(obj, function (key, value) {
					var k = array ? getTraceKey(value) : key;

					fragments.push({
						key: k,
						val: value,
						index: i++
					});
					ids.push(k);
				});
				instance.fragments = fragments;
			} else {
				avalon.each(obj, function (key, value) {
					if (!(key in $$skipArray)) {
						var k = array ? getTraceKey(value) : key;
						fragments.push(new VFragment([], k, value, i++));
						ids.push(k);
					}
				});
				instance.fragments = fragments;
			}
			return ids.join(';;');
		} else {
			return NaN;
		}
	}

	function mountList(instance) {
		var args = instance.fragments.map(function (fragment, index) {
			FragmentDecorator(fragment, instance, index);
			saveInCache(instance.cache, fragment);
			return fragment;
		});
		var list = instance.parentChildren;
		var i = list.indexOf(instance.begin);
		list.splice.apply(list, [i + 1, 0].concat(args));
	}

	function diffList(instance) {
		var cache = instance.cache;
		var newCache = {};
		var fuzzy = [];
		var list = instance.preFragments;

		list.forEach(function (el) {
			el._dispose = true;
		});

		instance.fragments.forEach(function (c, index) {
			var fragment = isInCache(cache, c.key);
			//取出之前的文档碎片
			if (fragment) {
				delete fragment._dispose;
				fragment.oldIndex = fragment.index;
				fragment.index = index; // 相当于 c.index

				resetVM(fragment.vm, instance.keyName);
				fragment.vm[instance.valName] = c.val;
				fragment.vm[instance.keyName] = instance.isArray ? index : fragment.key;
				saveInCache(newCache, fragment);
			} else {
				//如果找不到就进行模糊搜索
				fuzzy.push(c);
			}
		});
		fuzzy.forEach(function (c) {
			var fragment = fuzzyMatchCache(cache, c.key);
			if (fragment) {
				//重复利用
				fragment.oldIndex = fragment.index;
				fragment.key = c.key;
				var val = fragment.val = c.val;
				var index = fragment.index = c.index;

				fragment.vm[instance.valName] = val;
				fragment.vm[instance.keyName] = instance.isArray ? index : fragment.key;
				delete fragment._dispose;
			} else {

				c = new VFragment([], c.key, c.val, c.index);
				fragment = FragmentDecorator(c, instance, c.index);
				list.push(fragment);
			}
			saveInCache(newCache, fragment);
		});

		instance.fragments = list;
		list.sort(function (a, b) {
			return a.index - b.index;
		});
		instance.cache = newCache;
	}

	function resetVM(vm, a, b) {
		if (avalon.config.inProxyMode) {
			vm.$accessors[a].value = NaN;
		} else {
			vm.$accessors[a].set(NaN);
		}
	}

	function updateList(instance) {
		var before = instance.begin.dom;
		var parent = before.parentNode;
		var list = instance.fragments;
		var end = instance.end.dom;

		for (var i = 0, item; item = list[i]; i++) {
			if (item._dispose) {
				list.splice(i, 1);
				i--;
				item.dispose();
				continue;
			}
			if (item.oldIndex !== item.index) {
				var f = item.toFragment();
				var isEnd = before.nextSibling === null;
				parent.insertBefore(f, before.nextSibling);
				if (isEnd && !parent.contains(end)) {
					parent.insertBefore(end, before.nextSibling);
				}
			}
			before = item.split;
		}
		var ch = instance.parentChildren;
		var startIndex = ch.indexOf(instance.begin);
		var endIndex = ch.indexOf(instance.end);

		list.splice.apply(ch, [startIndex + 1, endIndex - startIndex].concat(list));
		if (parent.nodeName === 'SELECT' && parent._ms_duplex_) {
			updateView['select'].call(parent._ms_duplex_);
		}
	}

	/**
	 *
	 * @param {type} fragment
	 * @param {type} this
	 * @param {type} index
	 * @returns { key, val, index, oldIndex, this, dom, split, vm}
	 */
	function FragmentDecorator(fragment, instance, index) {
		var data = {};
		data[instance.keyName] = instance.isArray ? index : fragment.key;
		data[instance.valName] = fragment.val;
		if (instance.asName) {
			data[instance.asName] = instance.value;
		}
		var vm = fragment.vm = platform.itemFactory(instance.vm, {
			data: data
		});
		if (instance.isArray) {
			vm.$watch(instance.valName, function (a) {
				if (instance.value && instance.value.set) {
					instance.value.set(vm[instance.keyName], a);
				}
			});
		} else {
			vm.$watch(instance.valName, function (a) {
				instance.value[fragment.key] = a;
			});
		}

		fragment.index = index;
		fragment.innerRender = avalon.scan(instance.fragment, vm, function () {
			var oldRoot = this.root;
			ap.push.apply(fragment.children, oldRoot.children);
			this.root = fragment;
		});
		return fragment;
	}
	// 新位置: 旧位置
	function isInCache(cache, id) {
		var c = cache[id];
		if (c) {
			var arr = c.arr;
			/* istanbul ignore if*/
			if (arr) {
				var r = arr.pop();
				if (!arr.length) {
					c.arr = 0;
				}
				return r;
			}
			delete cache[id];
			return c;
		}
	}
	//[1,1,1] number1 number1_ number1__
	function saveInCache(cache, component) {
		var trackId = component.key;
		if (!cache[trackId]) {
			cache[trackId] = component;
		} else {
			var c = cache[trackId];
			var arr = c.arr || (c.arr = []);
			arr.push(component);
		}
	}

	function fuzzyMatchCache(cache) {
		var key;
		for (var id in cache) {
			var key = id;
			break;
		}
		if (key) {
			return isInCache(cache, key);
		}
	}

	//根据VM的属性值或表达式的值切换类名，ms-class='xxx yyy zzz:flag'
	//http://www.cnblogs.com/rubylouvre/archive/2012/12/17/2818540.html
	function classNames() {
		var classes = [];
		for (var i = 0; i < arguments.length; i++) {
			var arg = arguments[i];
			var argType = typeof arg;
			if (argType === 'string' || argType === 'number' || arg === true) {
				classes.push(arg);
			} else if (Array.isArray(arg)) {
				classes.push(classNames.apply(null, arg));
			} else if (argType === 'object') {
				for (var key in arg) {
					if (arg.hasOwnProperty(key) && arg[key]) {
						classes.push(key);
					}
				}
			}
		}

		return classes.join(' ');
	}

	avalon.directive('class', {
		diff: function diff(newVal, oldVal) {
			var type = this.type;
			var vdom = this.node;
			var classEvent = vdom.classEvent || {};
			if (type === 'hover') {
				//在移出移入时切换类名
				classEvent.mouseenter = activateClass;
				classEvent.mouseleave = abandonClass;
			} else if (type === 'active') {
				//在获得焦点时切换类名
				classEvent.tabIndex = vdom.props.tabindex || -1;
				classEvent.mousedown = activateClass;
				classEvent.mouseup = abandonClass;
				classEvent.mouseleave = abandonClass;
			}
			vdom.classEvent = classEvent;

			var className = classNames(newVal);

			if (typeof oldVal === void 0 || oldVal !== className) {
				this.value = className;

				vdom['change-' + type] = className;
				return true;
			}
		},
		update: function update(vdom, value) {
			var dom = vdom.dom;
			if (dom && dom.nodeType == 1) {

				var dirType = this.type;
				var change = 'change-' + dirType;
				var classEvent = vdom.classEvent;
				if (classEvent) {
					for (var i in classEvent) {
						if (i === 'tabIndex') {
							dom[i] = classEvent[i];
						} else {
							avalon.bind(dom, i, classEvent[i]);
						}
					}
					vdom.classEvent = {};
				}
				var names = ['class', 'hover', 'active'];
				names.forEach(function (type) {
					if (dirType !== type) return;
					if (type === 'class') {
						dom && setClass(dom, value);
					} else {
						var oldClass = dom.getAttribute(change);
						if (oldClass) {
							avalon(dom).removeClass(oldClass);
						}
						var name = 'change-' + type;
						dom.setAttribute(name, value);
					}
				});
			}
		}
	});

	directives.active = directives.hover = directives['class'];

	var classMap = {
		mouseenter: 'change-hover',
		mouseleave: 'change-hover',
		mousedown: 'change-active',
		mouseup: 'change-active'
	};

	function activateClass(e) {
		var elem = e.target;
		avalon(elem).addClass(elem.getAttribute(classMap[e.type]) || '');
	}

	function abandonClass(e) {
		var elem = e.target;
		var name = classMap[e.type];
		avalon(elem).removeClass(elem.getAttribute(name) || '');
		if (name !== 'change-active') {
			avalon(elem).removeClass(elem.getAttribute('change-active') || '');
		}
	}

	function setClass(dom, neo) {
		var old = dom.getAttribute('change-class');
		if (old !== neo) {
			avalon(dom).removeClass(old).addClass(neo);
			dom.setAttribute('change-class', neo);
		}
	}

	getLongID(activateClass);
	getLongID(abandonClass);

	/*
     * 通过绑定事件同步vmodel
     * 总共有三种方式同步视图
     * 1. 各种事件 input, change, click, propertychange, keydown...
     * 2. value属性重写
     * 3. 定时器轮询
     */

	function updateDataEvents(dom, data) {
		var events = {};
		//添加需要监听的事件
		switch (data.dtype) {
			case 'radio':
			case 'checkbox':
				events.click = updateDataHandle;
				break;
			case 'select':
				events.change = updateDataHandle;
				break;
			case 'contenteditable':
				/* istanbul ignore if */
				if (data.isChanged) {
					events.blur = updateDataHandle;
					/* istanbul ignore else */
				} else {
					/* istanbul ignore if*/

					if (avalon.modern) {
						if (window$1.webkitURL) {
							// http://code.metager.de/source/xref/WebKit/LayoutTests/fast/events/
							// https://bugs.webkit.org/show_bug.cgi?id=110742
							events.webkitEditableContentChanged = updateDataHandle;
						} else if (window$1.MutationEvent) {
							events.DOMCharacterDataModified = updateDataHandle;
						}
						events.input = updateDataHandle;
						/* istanbul ignore else */
					} else {
						events.keydown = updateModelKeyDown;
						events.paste = updateModelDelay;
						events.cut = updateModelDelay;
						events.focus = closeComposition;
						events.blur = openComposition;
					}
				}
				break;
			case 'input':
				/* istanbul ignore if */
				if (data.isChanged) {
					events.change = updateDataHandle;
					/* istanbul ignore else */
				} else {
					//http://www.cnblogs.com/rubylouvre/archive/2013/02/17/2914604.html
					//http://www.matts411.com/post/internet-explorer-9-oninput/
					if (msie$1 < 10) {
						//IE6-8的propertychange有问题,第一次用JS修改值时不会触发,而且你是全部清空value也不会触发
						//IE9的propertychange不支持自动完成,退格,删除,复制,贴粘,剪切或点击右边的小X的清空操作
						events.propertychange = updateModelHack;
						events.paste = updateModelDelay;
						events.cut = updateModelDelay;
						//IE9在第一次删除字符时不会触发oninput
						events.keyup = updateModelKeyDown;
					} else {
						events.input = updateDataHandle;
						events.compositionstart = openComposition;
						//微软拼音输入法的问题需要在compositionend事件中处理
						events.compositionend = closeComposition;
						//https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray
						//处理低版本的标准浏览器,通过Int8Array进行区分
						if (!/\[native code\]/.test(window$1.Int8Array)) {
							events.keydown = updateModelKeyDown; //safari < 5 opera < 11
							events.paste = updateModelDelay; //safari < 5
							events.cut = updateModelDelay; //safari < 5
							if (window$1.netscape) {
								// Firefox <= 3.6 doesn't fire the 'input' event when text is filled in through autocomplete
								events.DOMAutoComplete = updateDataHandle;
							}
						}
					}
				}
				break;
		}

		if (/password|text/.test(dom.type)) {
			events.focus = openCaret; //判定是否使用光标修正功能
			events.blur = closeCaret;
			data.getCaret = getCaret;
			data.setCaret = setCaret;
		}

		for (var name in events) {
			avalon.bind(dom, name, events[name]);
		}
	}

	function updateModelHack(e) {
		if (e.propertyName === 'value') {
			updateDataHandle.call(this, e);
		}
	}

	function updateModelDelay(e) {
		var elem = this;
		setTimeout(function () {
			updateDataHandle.call(elem, e);
		}, 0);
	}

	function openCaret() {
		this.caret = true;
	}
	/* istanbul ignore next */
	function closeCaret() {
		this.caret = false;
	}
	/* istanbul ignore next */
	function openComposition() {
		this.composing = true;
	}
	/* istanbul ignore next */
	function closeComposition(e) {
		this.composing = false;
		updateModelDelay.call(this, e);
	}
	/* istanbul ignore next */
	function updateModelKeyDown(e) {
		var key = e.keyCode;
		// ignore
		//    command            modifiers                   arrows
		if (key === 91 || 15 < key && key < 19 || 37 <= key && key <= 40) return;
		updateDataHandle.call(this, e);
	}

	getShortID(openCaret);
	getShortID(closeCaret);
	getShortID(openComposition);
	getShortID(closeComposition);
	getShortID(updateDataHandle);
	getShortID(updateModelHack);
	getShortID(updateModelDelay);
	getShortID(updateModelKeyDown);

	//IE6-8要处理光标时需要异步
	var mayBeAsync = function mayBeAsync(fn) {
		setTimeout(fn, 0);
	};
	/* istanbul ignore next */
	function setCaret(target, cursorPosition) {
		var range$$1;
		if (target.createTextRange) {
			mayBeAsync(function () {
				target.focus();
				range$$1 = target.createTextRange();
				range$$1.collapse(true);
				range$$1.moveEnd('character', cursorPosition);
				range$$1.moveStart('character', cursorPosition);
				range$$1.select();
			});
		} else {
			target.focus();
			if (target.selectionStart !== undefined) {
				target.setSelectionRange(cursorPosition, cursorPosition);
			}
		}
	}
	/* istanbul ignore next*/
	function getCaret(target) {
		var start = 0;
		var normalizedValue;
		var range$$1;
		var textInputRange;
		var len;
		var endRange;

		if (target.selectionStart + target.selectionEnd > -1) {
			start = target.selectionStart;
		} else {
			range$$1 = document$1.selection.createRange();

			if (range$$1 && range$$1.parentElement() === target) {
				len = target.value.length;
				normalizedValue = target.value.replace(/\r\n/g, '\n');

				textInputRange = target.createTextRange();
				textInputRange.moveToBookmark(range$$1.getBookmark());

				endRange = target.createTextRange();
				endRange.collapse(false);

				if (textInputRange.compareEndPoints('StartToEnd', endRange) > -1) {
					start = len;
				} else {
					start = -textInputRange.moveStart('character', -len);
					start += normalizedValue.slice(0, start).split('\n').length - 1;
				}
			}
		}

		return start;
	}

	avalon.directive('duplex', {
		priority: 9999999,
		beforeInit: duplexBeforeInit,
		init: duplexInit,
		diff: duplexDiff,
		update: function update(vdom, value) {
			if (!this.dom) {
				duplexBind.call(this, vdom, updateDataEvents);
			}
			//如果不支持input.value的Object.defineProperty的属性支持,
			//需要通过轮询同步, chrome 42及以下版本需要这个hack
			pollValue.call(this, avalon.msie, valueHijack);
			//更新视图

			updateView[this.dtype].call(this);
		}
	});

	function pollValue(isIE, valueHijack$$1) {
		var dom = this.dom;
		if (this.isString && valueHijack$$1 && !isIE && !dom.valueHijack) {
			dom.valueHijack = updateDataHandle;
			var intervalID = setInterval(function () {
				if (!avalon.contains(avalon.root, dom)) {
					clearInterval(intervalID);
				} else {
					dom.valueHijack({ type: 'poll' });
				}
			}, 30);
			return intervalID;
		}
	}
	avalon.__pollValue = pollValue; //export to test
	/* istanbul ignore if */
	if (avalon.msie < 8) {
		var oldUpdate = updateView.updateChecked;
		updateView.updateChecked = function (vdom, checked) {
			var dom = vdom.dom;
			if (dom) {
				setTimeout(function () {
					oldUpdate(vdom, checked);
					dom.firstCheckedIt = 1;
				}, dom.firstCheckedIt ? 31 : 16);
				//IE6,7 checkbox, radio是使用defaultChecked控制选中状态，
				//并且要先设置defaultChecked后设置checked
				//并且必须设置延迟(因为必须插入DOM树才生效)
			}
		};
	}

	avalon.directive('rules', {
		diff: function diff(rules) {
			if (isObject(rules)) {
				var vdom = this.node;
				vdom.rules = platform.toJson(rules);
				return true;
			}
		}
	});
	function isRegExp(value) {
		return avalon.type(value) === 'regexp';
	}
	var rmail = /^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/i;
	var rurl = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/;
	function isCorrectDate(value) {
		if (typeof value === "string" && value) {
			//是字符串但不能是空字符
			var arr = value.split("-"); //可以被-切成3份，并且第1个是4个字符
			if (arr.length === 3 && arr[0].length === 4) {
				var year = ~~arr[0]; //全部转换为非负整数
				var month = ~~arr[1] - 1;
				var date = ~~arr[2];
				var d = new Date(year, month, date);
				return d.getFullYear() === year && d.getMonth() === month && d.getDate() === date;
			}
		}
		return false;
	}
	//https://github.com/adform/validator.js/blob/master/validator.js
	avalon.shadowCopy(avalon.validators, {
		pattern: {
			message: '必须匹配{{pattern}}这样的格式',
			get: function get(value, field, next) {
				var elem = field.dom;
				var data = field.data;
				if (!isRegExp(data.pattern)) {
					var h5pattern = elem.getAttribute("pattern");
					data.pattern = new RegExp('^(?:' + h5pattern + ')$');
				}
				next(data.pattern.test(value));
				return value;
			}
		},
		digits: {
			message: '必须整数',
			get: function get(value, field, next) {
				//整数
				next(/^\-?\d+$/.test(value));
				return value;
			}
		},
		number: {
			message: '必须数字',
			get: function get(value, field, next) {
				//数值
				next(!!value && isFinite(value)); // isFinite('') --> true
				return value;
			}
		},
		norequired: {
			message: '',
			get: function get(value, field, next) {
				next(true);
				return value;
			}
		},
		required: {
			message: '必须填写',
			get: function get(value, field, next) {
				next(value !== '');
				return value;
			}
		},
		equalto: {
			message: '密码输入不一致',
			get: function get(value, field, next) {
				var id = String(field.data.equalto);
				var other = avalon(document.getElementById(id)).val() || "";
				next(value === other);
				return value;
			}
		},
		date: {
			message: '日期格式不正确',
			get: function get(value, field, next) {
				var data = field.data;
				if (isRegExp(data.date)) {
					next(data.date.test(value));
				} else {
					next(isCorrectDate(value));
				}
				return value;
			}
		},
		url: {
			message: 'URL格式不正确',
			get: function get(value, field, next) {
				next(rurl.test(value));
				return value;
			}
		},
		email: {
			message: 'email格式不正确',
			get: function get(value, field, next) {
				next(rmail.test(value));
				return value;
			}
		},
		minlength: {
			message: '最少输入{{minlength}}个字',
			get: function get(value, field, next) {
				var num = parseInt(field.data.minlength, 10);
				next(value.length >= num);
				return value;
			}
		},
		maxlength: {
			message: '最多输入{{maxlength}}个字',
			get: function get(value, field, next) {
				var num = parseInt(field.data.maxlength, 10);
				next(value.length <= num);
				return value;
			}
		},
		min: {
			message: '输入值不能小于{{min}}',
			get: function get(value, field, next) {
				var num = parseInt(field.data.min, 10);
				next(parseFloat(value) >= num);
				return value;
			}
		},
		max: {
			message: '输入值不能大于{{max}}',
			get: function get(value, field, next) {
				var num = parseInt(field.data.max, 10);
				next(parseFloat(value) <= num);
				return value;
			}
		},
		chs: {
			message: '必须是中文字符',
			get: function get(value, field, next) {
				next(/^[\u4e00-\u9fa5]+$/.test(value));
				return value;
			}
		}
	});

	var valiDir = avalon.directive('validate', {
		diff: function diff(validator) {
			var vdom = this.node;
			if (vdom.validator) {
				return;
			}
			if (isObject(validator)) {
				//注意，这个Form标签的虚拟DOM有两个验证对象
				//一个是vmValidator，它是用户VM上的那个原始子对象，也是一个VM
				//一个是validator，它是vmValidator.$model， 这是为了防止IE6－8添加子属性时添加的hack
				//也可以称之为safeValidate
				vdom.validator = validator;
				validator = platform.toJson(validator);
				validator.vdom = vdom;
				validator.dom = vdom.dom;

				for (var name in valiDir.defaults) {
					if (!validator.hasOwnProperty(name)) {
						validator[name] = valiDir.defaults[name];
					}
				}
				validator.fields = validator.fields || [];
				vdom.vmValidator = validator;
				return true;
			}
		},
		update: function update(vdom) {

			var vmValidator = vdom.vmValidator;
			var validator = vdom.validator;
			var dom = vdom.dom;
			dom._ms_validate_ = vmValidator;

			collectFeild(vdom.children, vmValidator.fields, vmValidator);
			var type = window.netscape ? 'keypress' : 'focusin';
			avalon.bind(document, type, findValidator);
			//为了方便用户手动执行验证，我们需要为原始vmValidate上添加一个onManual方法
			function onManual() {
				var v = this;
				v && valiDir.validateAll.call(v, v.onValidateAll);
			}

			try {
				var fn = vmValidator.onManual = onManual.bind(vmValidator);
				validator.onManual = fn;
			} catch (e) {
				avalon.warn('要想使用onManual方法，必须在validate对象预定义一个空的onManual函数');
			}
			delete vdom.vmValidator;

			dom.setAttribute('novalidate', 'novalidate');

			/* istanbul ignore if */
			if (vmValidator.validateAllInSubmit) {
				avalon.bind(dom, 'submit', validateAllInSubmitFn);
			}
		},
		validateAll: function validateAll(callback) {
			var validator = this;
			var vdom = this.vdom;
			var fields = validator.fields = [];
			collectFeild(vdom.children, fields, validator);
			var fn = typeof callback === 'function' ? callback : validator.onValidateAll;
			var promises = validator.fields.filter(function (field) {
				var el = field.dom;
				return el && !el.disabled && validator.dom.contains(el);
			}).map(function (field) {
				return valiDir.validate(field, true);
			});
			var uniq = {};
			return Promise.all(promises).then(function (array) {
				var reasons = array.concat.apply([], array);
				if (validator.deduplicateInValidateAll) {
					reasons = reasons.filter(function (reason) {
						var el = reason.element;
						var uuid = el.uniqueID || (el.uniqueID = setTimeout('1'));
						if (uniq[uuid]) {
							return false;
						} else {
							return uniq[uuid] = true;
						}
					});
				}
				fn.call(vdom.dom, reasons); //这里只放置未通过验证的组件
			});
		},

		validate: function validate(field, isValidateAll, event) {

			var promises = [];
			var value = field.value;
			var elem = field.dom;
			/* istanbul ignore if */
			if (typeof Promise !== 'function') {
				//avalon-promise不支持phantomjs
				avalon.warn('浏览器不支持原生Promise,请下载并<script src=url>引入\nhttps://github.com/RubyLouvre/avalon/blob/master/test/promise.js');
			}
			/* istanbul ignore if */
			if (elem.disabled) return;
			var rules = field.vdom.rules;
			var ngs = [],
				isOk = true;
			if (!(rules.norequired && value === '')) {
				for (var ruleName in rules) {
					var ruleValue = rules[ruleName];
					if (ruleValue === false) continue;
					var hook = avalon.validators[ruleName];
					var resolve;
					promises.push(new Promise(function (a, b) {
						resolve = a;
					}));
					var next = function next(a) {
						var reason = {
							element: elem,
							data: field.data,
							message: elem.getAttribute('data-' + ruleName + '-message') || elem.getAttribute('data-message') || hook.message,
							validateRule: ruleName,
							getMessage: getMessage
						};
						if (a) {
							resolve(true);
						} else {
							isOk = false;
							ngs.push(reason);
							resolve(false);
						}
					};
					field.data = {};
					field.data[ruleName] = ruleValue;
					hook.get(value, field, next);
				}
			}

			//如果promises不为空，说明经过验证拦截器
			return Promise.all(promises).then(function (array) {
				if (!isValidateAll) {
					var validator = field.validator;
					if (isOk) {
						validator.onSuccess.call(elem, [{
							data: field.data,
							element: elem
						}], event);
					} else {
						validator.onError.call(elem, ngs, event);
					}
					validator.onComplete.call(elem, ngs, event);
				}
				return ngs;
			});
		}
	});

	//https://github.com/RubyLouvre/avalon/issues/1977
	function getValidate(dom) {
		while (dom.tagName !== 'FORM') {
			dom = dom.parentNode;
		}
		return dom._ms_validate_;
	}

	function validateAllInSubmitFn(e) {
		e.preventDefault();
		var v = getValidate(e.target);
		if (v && v.onManual) {
			v.onManual();
		}
	}

	function collectFeild(nodes, fields, validator) {
		for (var i = 0, vdom; vdom = nodes[i++];) {
			var duplex = vdom.rules && vdom.duplex;
			if (duplex) {
				fields.push(duplex);
				bindValidateEvent(duplex, validator);
			} else if (vdom.children) {
				collectFeild(vdom.children, fields, validator);
			} else if (Array.isArray(vdom)) {
				collectFeild(vdom, fields, validator);
			}
		}
	}

	function findValidator(e) {
		var dom = e.target;
		var duplex = dom._ms_duplex_;
		var vdom = (duplex || {}).vdom;
		if (duplex && vdom.rules && !duplex.validator) {
			var msValidator = getValidate(dom);
			if (msValidator && avalon.Array.ensure(msValidator.fields, duplex)) {
				bindValidateEvent(duplex, msValidator);
			}
		}
	}

	function singleValidate(e) {
		var dom = e.target;
		var duplex = dom._ms_duplex_;
		var msValidator = getValidate(e.target);
		msValidator && msValidator.validate(duplex, 0, e);
	}

	function bindValidateEvent(field, validator) {

		var node = field.dom;
		if (field.validator) {
			return;
		}
		field.validator = validator;
		/* istanbul ignore if */
		if (validator.validateInKeyup && !field.isChanged && !field.debounceTime) {
			avalon.bind(node, 'keyup', singleValidate);
		}
		/* istanbul ignore if */
		if (validator.validateInBlur) {
			avalon.bind(node, 'blur', singleValidate);
		}
		/* istanbul ignore if */
		if (validator.resetInFocus) {
			avalon.bind(node, 'focus', function (e) {
				var dom = e.target;
				var field = dom._ms_duplex_;
				var validator = getValidate(e.target);
				validator && validator.onReset.call(dom, e, field);
			});
		}
	}
	var rformat = /\\?{{([^{}]+)\}}/gm;

	function getMessage() {
		var data = this.data || {};
		return this.message.replace(rformat, function (_, name) {
			return data[name] == null ? '' : data[name];
		});
	}
	valiDir.defaults = {
		validate: valiDir.validate,
		onError: avalon.noop,
		onSuccess: avalon.noop,
		onComplete: avalon.noop,
		onManual: avalon.noop,
		onReset: avalon.noop,
		onValidateAll: avalon.noop,
		validateInBlur: true, //@config {Boolean} true，在blur事件中进行验证,触发onSuccess, onError, onComplete回调
		validateInKeyup: true, //@config {Boolean} true，在keyup事件中进行验证,触发onSuccess, onError, onComplete回调
		validateAllInSubmit: true, //@config {Boolean} true，在submit事件中执行onValidateAll回调
		resetInFocus: true, //@config {Boolean} true，在focus事件中执行onReset回调,
		deduplicateInValidateAll: false //@config {Boolean} false，在validateAll回调中对reason数组根据元素节点进行去重
	};

	/**
	 * 一个directive装饰器
	 * @returns {directive}
	 */
	// DirectiveDecorator(scope, binding, vdom, this)
	// Decorator(vm, options, callback)
	function Directive(vm, binding, vdom, render) {
		var type = binding.type;
		var decorator = avalon.directives[type];
		if (inBrowser) {
			var dom = avalon.vdom(vdom, 'toDOM');
			if (dom.nodeType === 1) {
				dom.removeAttribute(binding.attrName);
			}
			vdom.dom = dom;
		}
		var callback = decorator.update ? function (value) {
			if (!render.mount && /css|visible|duplex/.test(type)) {
				render.callbacks.push(function () {
					decorator.update.call(directive$$1, directive$$1.node, value);
				});
			} else {
				decorator.update.call(directive$$1, directive$$1.node, value);
			}
		} : avalon.noop;
		for (var key in decorator) {
			binding[key] = decorator[key];
		}
		binding.node = vdom;
		var directive$$1 = new Action(vm, binding, callback);
		if (directive$$1.init) {
			//这里可能会重写node, callback, type, name
			directive$$1.init();
		}
		directive$$1.update();
		return directive$$1;
	}

	var eventMap = avalon.oneObject('animationend,blur,change,input,' + 'click,dblclick,focus,keydown,keypress,keyup,mousedown,mouseenter,' + 'mouseleave,mousemove,mouseout,mouseover,mouseup,scan,scroll,submit', 'on');
	function parseAttributes(dirs, tuple) {
		var node = tuple[0],
			uniq = {},
			bindings = [];
		var hasIf = false;
		for (var name in dirs) {
			var value = dirs[name];
			var arr = name.split('-');
			// ms-click
			if (name in node.props) {
				var attrName = name;
			} else {
				attrName = ':' + name.slice(3);
			}
			if (eventMap[arr[1]]) {
				arr.splice(1, 0, 'on');
			}
			//ms-on-click
			if (arr[1] === 'on') {
				arr[3] = parseFloat(arr[3]) || 0;
			}

			var type = arr[1];
			if (type === 'controller' || type === 'important') continue;
			if (directives[type]) {

				var binding = {
					type: type,
					param: arr[2],
					attrName: attrName,
					name: arr.join('-'),
					expr: value,
					priority: directives[type].priority || type.charCodeAt(0) * 100
				};
				if (type === 'if') {
					hasIf = true;
				}
				if (type === 'on') {
					binding.priority += arr[3];
				}
				if (!uniq[binding.name]) {
					uniq[binding.name] = value;
					bindings.push(binding);
					if (type === 'for') {
						return [avalon.mix(binding, tuple[3])];
					}
				}
			}
		}
		bindings.sort(byPriority);

		if (hasIf) {
			var ret = [];
			for (var i = 0, el; el = bindings[i++];) {
				ret.push(el);
				if (el.type === 'if') {
					return ret;
				}
			}
		}
		return bindings;
	}
	function byPriority(a, b) {
		return a.priority - b.priority;
	}

	var rimprovePriority = /[+-\?]/;
	var rinnerValue = /__value__\)$/;
	function parseInterpolate(dir) {
		var rlineSp = /\n\r?/g;
		var str = dir.nodeValue.trim().replace(rlineSp, '');
		var tokens = [];
		do {
			//aaa{{@bbb}}ccc
			var index = str.indexOf(config.openTag);
			index = index === -1 ? str.length : index;
			var value = str.slice(0, index);
			if (/\S/.test(value)) {
				tokens.push(avalon.quote(avalon._decode(value)));
			}
			str = str.slice(index + config.openTag.length);
			if (str) {
				index = str.indexOf(config.closeTag);
				var value = str.slice(0, index);
				var expr = avalon.unescapeHTML(value);
				if (/\|\s*\w/.test(expr)) {
					//如果存在过滤器，优化干掉
					var arr = addScope(expr, 'expr');
					if (arr[1]) {
						expr = arr[1].replace(rinnerValue, arr[0] + ')');
					}
				}
				if (rimprovePriority) {
					expr = '(' + expr + ')';
				}
				tokens.push(expr);

				str = str.slice(index + config.closeTag.length);
			}
		} while (str.length);
		return [{
			expr: tokens.join('+'),
			name: 'expr',
			type: 'expr'
		}];
	}

	function getChildren(arr) {
		var count = 0;
		for (var i = 0, el; el = arr[i++];) {
			if (el.nodeName === '#document-fragment') {
				count += getChildren(el.children);
			} else {
				count += 1;
			}
		}
		return count;
	}
	function groupTree(parent, children) {
		children && children.forEach(function (vdom) {
			if (!vdom) return;
			var vlength = vdom.children && getChildren(vdom.children);
			if (vdom.nodeName === '#document-fragment') {
				var dom = createFragment();
			} else {
				dom = avalon.vdom(vdom, 'toDOM');
				var domlength = dom.childNodes && dom.childNodes.length;
				if (domlength && vlength && domlength > vlength) {
					if (!appendChildMayThrowError[dom.nodeName]) {
						avalon.clearHTML(dom);
					}
				}
			}
			if (vlength) {
				groupTree(dom, vdom.children);
				if (vdom.nodeName === 'select') {
					var values = [];
					getSelectedValue(vdom, values);
					lookupOption(vdom, values);
				}
			}
			//高级版本可以尝试 querySelectorAll

			try {
				if (!appendChildMayThrowError[parent.nodeName]) {
					parent.appendChild(dom);
				}
			} catch (e) {}
		});
	}

	function dumpTree(elem) {
		if (elem) {
			var firstChild;
			while (firstChild = elem.firstChild) {
				if (firstChild.nodeType === 1) {
					dumpTree(firstChild);
				}
				elem.removeChild(firstChild);
			}
		}
	}

	function getRange(childNodes, node) {
		var i = childNodes.indexOf(node) + 1;
		var deep = 1,
			nodes = [],
			end;
		nodes.start = i;
		while (node = childNodes[i++]) {
			nodes.push(node);
			if (node.nodeName === '#comment') {
				if (startWith(node.nodeValue, 'ms-for:')) {
					deep++;
				} else if (node.nodeValue === 'ms-for-end:') {
					deep--;
					if (deep === 0) {
						end = node;
						nodes.pop();
						break;
					}
				}
			}
		}
		nodes.end = end;
		return nodes;
	}

	function startWith(long, short) {
		return long.indexOf(short) === 0;
	}

	var appendChildMayThrowError = {
		'#text': 1,
		'#comment': 1,
		script: 1,
		style: 1,
		noscript: 1
	};

	/**
	 * 生成一个渲染器,并作为它第一个遇到的ms-controller对应的VM的$render属性
	 * @param {String|DOM} node
	 * @param {ViewModel|Undefined} vm
	 * @param {Function|Undefined} beforeReady
	 * @returns {Render}
	 */
	avalon.scan = function (node, vm, beforeReady) {
		return new Render(node, vm, beforeReady || avalon.noop);
	};

	/**
	 * avalon.scan 的内部实现
	 */
	function Render(node, vm, beforeReady) {
		this.root = node; //如果传入的字符串,确保只有一个标签作为根节点
		this.vm = vm;
		this.beforeReady = beforeReady;
		this.bindings = []; //收集待加工的绑定属性
		this.callbacks = [];
		this.directives = [];
		this.init();
	}

	Render.prototype = {
		/**
		 * 开始扫描指定区域
		 * 收集绑定属性
		 * 生成指令并建立与VM的关联
		 */
		init: function init() {
			var vnodes;
			if (this.root && this.root.nodeType > 0) {
				vnodes = fromDOM(this.root); //转换虚拟DOM
				//将扫描区域的每一个节点与其父节点分离,更少指令对DOM操作时,对首屏输出造成的频繁重绘
				dumpTree(this.root);
			} else if (typeof this.root === 'string') {
				vnodes = fromString(this.root); //转换虚拟DOM
			} else {
				return avalon.warn('avalon.scan first argument must element or HTML string');
			}

			this.root = vnodes[0];
			this.vnodes = vnodes;
			this.scanChildren(vnodes, this.vm, true);
		},
		scanChildren: function scanChildren(children, scope, isRoot) {
			for (var i = 0; i < children.length; i++) {
				var vdom = children[i];
				switch (vdom.nodeName) {
					case '#text':
						scope && this.scanText(vdom, scope);
						break;
					case '#comment':
						scope && this.scanComment(vdom, scope, children);
						break;
					case '#document-fragment':
						this.scanChildren(vdom.children, scope, false);
						break;
					default:
						this.scanTag(vdom, scope, children, false);
						break;
				}
			}
			if (isRoot) {
				this.complete();
			}
		},


		/**
		 * 从文本节点获取指令
		 * @param {type} vdom
		 * @param {type} scope
		 * @returns {undefined}
		 */
		scanText: function scanText(vdom, scope) {
			if (config.rexpr.test(vdom.nodeValue)) {
				this.bindings.push([vdom, scope, {
					nodeValue: vdom.nodeValue
				}]);
			}
		},


		/**
		 * 从注释节点获取指令
		 * @param {type} vdom
		 * @param {type} scope
		 * @param {type} parentChildren
		 * @returns {undefined}
		 */
		scanComment: function scanComment(vdom, scope, parentChildren) {
			if (startWith(vdom.nodeValue, 'ms-for:')) {
				this.getForBinding(vdom, scope, parentChildren);
			}
		},


		/**
		 * 从元素节点的nodeName与属性中获取指令
		 * @param {type} vdom
		 * @param {type} scope
		 * @param {type} parentChildren
		 * @param {type} isRoot 用于执行complete方法
		 * @returns {undefined}
		 */
		scanTag: function scanTag(vdom, scope, parentChildren, isRoot) {
			var dirs = {},
				attrs = vdom.props,
				hasDir,
				hasFor;
			for (var attr in attrs) {
				var value = attrs[attr];
				var oldName = attr;
				if (attr.charAt(0) === ':') {
					attr = 'ms-' + attr.slice(1);
				}
				if (startWith(attr, 'ms-')) {
					dirs[attr] = value;
					var type = attr.match(/\w+/g)[1];
					type = eventMap[type] || type;
					if (!directives[type]) {
						avalon.warn(attr + ' has not registered!');
					}
					hasDir = true;
				}
				if (attr === 'ms-for') {
					hasFor = value;
					delete attrs[oldName];
				}
			}
			var $id = dirs['ms-important'] || dirs['ms-controller'];
			if ($id) {
				/**
				 * 后端渲染
				 * serverTemplates后端给avalon添加的对象,里面都是模板,
				 * 将原来后端渲染好的区域再还原成原始样子,再被扫描
				 */
				var templateCaches = avalon.serverTemplates;
				var temp = templateCaches && templateCaches[$id];
				if (temp) {
					avalon.log('前端再次渲染后端传过来的模板');
					var node = fromString(temp)[0];
					for (var i in node) {
						vdom[i] = node[i];
					}
					delete templateCaches[$id];
					this.scanTag(vdom, scope, parentChildren, isRoot);
					return;
				}
				//推算出指令类型
				var type = dirs['ms-important'] === $id ? 'important' : 'controller';
				//推算出用户定义时属性名,是使用ms-属性还是:属性
				var attrName = 'ms-' + type in attrs ? 'ms-' + type : ':' + type;

				if (inBrowser) {
					delete attrs[attrName];
				}
				var dir = directives[type];
				scope = dir.getScope.call(this, $id, scope);
				if (!scope) {
					return;
				} else {
					var clazz = attrs['class'];
					if (clazz) {
						attrs['class'] = (' ' + clazz + ' ').replace(' ms-controller ', '').trim();
					}
				}
				var render = this;
				scope.$render = render;
				this.callbacks.push(function () {
					//用于删除ms-controller
					dir.update.call(render, vdom, attrName, $id);
				});
			}
			if (hasFor) {
				if (vdom.dom) {
					vdom.dom.removeAttribute(oldName);
				}
				return this.getForBindingByElement(vdom, scope, parentChildren, hasFor);
			}

			if (/^ms\-/.test(vdom.nodeName)) {
				attrs.is = vdom.nodeName;
			}

			if (attrs['is']) {
				if (!dirs['ms-widget']) {
					dirs['ms-widget'] = '{}';
				}
				hasDir = true;
			}
			if (hasDir) {
				this.bindings.push([vdom, scope, dirs]);
			}
			var children = vdom.children;
			//如果存在子节点,并且不是容器元素(script, stype, textarea, xmp...)
			if (!orphanTag[vdom.nodeName] && children && children.length && !delayCompileNodes(dirs)) {
				this.scanChildren(children, scope, false);
			}
		},


		/**
		 * 将绑定属性转换为指令
		 * 执行各种回调与优化指令
		 * @returns {undefined}
		 */
		complete: function complete() {
			this.yieldDirectives();
			this.beforeReady();
			if (inBrowser) {
				var root$$1 = this.root;
				if (inBrowser) {
					var rootDom = avalon.vdom(root$$1, 'toDOM');
					groupTree(rootDom, root$$1.children);
				}
			}

			this.mount = true;
			var fn;
			while (fn = this.callbacks.pop()) {
				fn();
			}
			this.optimizeDirectives();
		},


		/**
		 * 将收集到的绑定属性进行深加工,最后转换指令
		 * @returns {Array<tuple>}
		 */
		yieldDirectives: function yieldDirectives() {
			var tuple;
			while (tuple = this.bindings.shift()) {
				var vdom = tuple[0],
					scope = tuple[1],
					dirs = tuple[2],
					bindings = [];
				if ('nodeValue' in dirs) {
					bindings = parseInterpolate(dirs);
				} else if (!('ms-skip' in dirs)) {
					bindings = parseAttributes(dirs, tuple);
				}
				for (var i = 0, binding; binding = bindings[i++];) {
					var dir = directives[binding.type];
					if (!inBrowser && /on|duplex|active|hover/.test(binding.type)) {
						continue;
					}
					if (dir.beforeInit) {
						dir.beforeInit.call(binding);
					}

					var directive$$1 = new Directive(scope, binding, vdom, this);
					this.directives.push(directive$$1);
				}
			}
		},


		/**
		 * 修改指令的update与callback方法,让它们以后执行时更加高效
		 * @returns {undefined}
		 */
		optimizeDirectives: function optimizeDirectives() {
			for (var i = 0, el; el = this.directives[i++];) {
				el.callback = directives[el.type].update;
				el.update = newUpdate;
				el._isScheduled = false;
			}
		},

		update: function update() {
			for (var i = 0, el; el = this.directives[i++];) {
				el.update();
			}
		},

		/**
		 * 销毁所有指令
		 * @returns {undefined}
		 */
		dispose: function dispose() {
			var list = this.directives || [];
			for (var i = 0, el; el = list[i++];) {
				el.dispose();
			}
			//防止其他地方的this.innerRender && this.innerRender.dispose报错
			for (var _i6 in this) {
				if (_i6 !== 'dispose') delete this[_i6];
			}
		},


		/**
		 * 将循环区域转换为for指令
		 * @param {type} begin 注释节点
		 * @param {type} scope
		 * @param {type} parentChildren
		 * @param {type} userCb 循环结束回调
		 * @returns {undefined}
		 */
		getForBinding: function getForBinding(begin, scope, parentChildren, userCb) {
			var expr = begin.nodeValue.replace('ms-for:', '').trim();
			begin.nodeValue = 'ms-for:' + expr;
			var nodes = getRange(parentChildren, begin);
			var end = nodes.end;
			var fragment = avalon.vdom(nodes, 'toHTML');
			parentChildren.splice(nodes.start, nodes.length);
			begin.props = {};
			this.bindings.push([begin, scope, {
				'ms-for': expr
			}, {
				begin: begin,
				end: end,
				expr: expr,
				userCb: userCb,
				fragment: fragment,
				parentChildren: parentChildren
			}]);
		},


		/**
		 * 在带ms-for元素节点旁添加两个注释节点,组成循环区域
		 * @param {type} vdom
		 * @param {type} scope
		 * @param {type} parentChildren
		 * @param {type} expr
		 * @returns {undefined}
		 */
		getForBindingByElement: function getForBindingByElement(vdom, scope, parentChildren, expr) {
			var index = parentChildren.indexOf(vdom); //原来带ms-for的元素节点
			var props = vdom.props;
			var begin = {
				nodeName: '#comment',
				nodeValue: 'ms-for:' + expr
			};
			if (props.slot) {
				begin.slot = props.slot;
				delete props.slot;
			}
			var end = {
				nodeName: '#comment',
				nodeValue: 'ms-for-end:'
			};
			parentChildren.splice(index, 1, begin, vdom, end);
			this.getForBinding(begin, scope, parentChildren, props['data-for-rendered']);
		}
	};
	var viewID;

	function newUpdate() {
		var oldVal = this.beforeUpdate();
		var newVal = this.value = this.get();
		if (this.callback && this.diff(newVal, oldVal)) {
			this.callback(this.node, this.value);
			var vm = this.vm;
			var $render = vm.$render;
			var list = vm.$events['onViewChange'];
			/* istanbul ignore if */
			if (list && $render && $render.root && !avalon.viewChanging) {
				if (viewID) {
					clearTimeout(viewID);
					viewID = null;
				}
				viewID = setTimeout(function () {
					list.forEach(function (el) {
						el.callback.call(vm, {
							type: 'viewchange',
							target: $render.root,
							vmodel: vm
						});
					});
				});
			}
		}
		this._isScheduled = false;
	}

	var events = 'onInit,onReady,onViewChange,onDispose,onEnter,onLeave';
	var componentEvents = avalon.oneObject(events);

	function toObject(value) {
		var value = platform.toJson(value);
		if (Array.isArray(value)) {
			var v = {};
			value.forEach(function (el) {
				el && avalon.shadowCopy(v, el);
			});
			return v;
		}
		return value;
	}
	var componentQueue = [];
	avalon.directive('widget', {
		delay: true,
		priority: 4,
		deep: true,
		init: function init() {
			//cached属性必须定义在组件容器里面,不是template中
			var vdom = this.node;
			this.cacheVm = !!vdom.props.cached;
			if (vdom.dom && vdom.nodeName === '#comment') {
				var comment = vdom.dom;
			}
			var oldValue = this.getValue();
			var value = toObject(oldValue);
			//外部VM与内部VM
			// ＝＝＝创建组件的VM＝＝BEGIN＝＝＝
			var is = vdom.props.is || value.is;
			this.is = is;
			var component = avalon.components[is];
			//外部传入的总大于内部
			if (!('fragment' in this)) {
				if (!vdom.isVoidTag) {
					//提取组件容器内部的东西作为模板
					var text = vdom.children[0];
					if (text && text.nodeValue) {
						this.fragment = text.nodeValue;
					} else {
						this.fragment = avalon.vdom(vdom.children, 'toHTML');
					}
				} else {
					this.fragment = false;
				}
			}
			//如果组件还没有注册，那么将原元素变成一个占位用的注释节点
			if (!component) {
				this.readyState = 0;
				vdom.nodeName = '#comment';
				vdom.nodeValue = 'unresolved component placeholder';
				delete vdom.dom;
				avalon.Array.ensure(componentQueue, this);
				return;
			}

			//如果是非空元素，比如说xmp, ms-*, template
			var id = value.id || value.$id;
			var hasCache = avalon.vmodels[id];
			var fromCache = false;
			// this.readyState = 1
			if (hasCache) {
				comVm = hasCache;
				this.comVm = comVm;
				replaceRoot(this, comVm.$render);
				fromCache = true;
			} else {
				if (typeof component === 'function') {
					component = new component(value);
				}
				var comVm = createComponentVm(component, value, is);
				this.readyState = 1;
				fireComponentHook(comVm, vdom, 'Init');
				this.comVm = comVm;

				// ＝＝＝创建组件的VM＝＝END＝＝＝
				var innerRender = avalon.scan(component.template, comVm);
				comVm.$render = innerRender;
				replaceRoot(this, innerRender);
				var nodesWithSlot = [];
				var directives$$1 = [];
				if (this.fragment || component.soleSlot) {
					var curVM = this.fragment ? this.vm : comVm;
					var curText = this.fragment || '{{##' + component.soleSlot + '}}';
					var childBoss = avalon.scan('<div>' + curText + '</div>', curVM, function () {
						nodesWithSlot = this.root.children;
					});
					directives$$1 = childBoss.directives;
					this.childBoss = childBoss;
					for (var i in childBoss) {
						delete childBoss[i];
					}
				}
				Array.prototype.push.apply(innerRender.directives, directives$$1);

				var arraySlot = [],
					objectSlot = {};
				//从用户写的元素内部 收集要移动到 新创建的组件内部的元素
				if (component.soleSlot) {
					arraySlot = nodesWithSlot;
				} else {
					nodesWithSlot.forEach(function (el, i) {
						//要求带slot属性
						if (el.slot) {
							var nodes = getRange(nodesWithSlot, el);
							nodes.push(nodes.end);
							nodes.unshift(el);
							objectSlot[el.slot] = nodes;
						} else if (el.props) {
							var name = el.props.slot;
							if (name) {
								delete el.props.slot;
								if (Array.isArray(objectSlot[name])) {
									objectSlot[name].push(el);
								} else {
									objectSlot[name] = [el];
								}
							}
						}
					});
				}
				//将原来元素的所有孩子，全部移动新的元素的第一个slot的位置上
				if (component.soleSlot) {
					insertArraySlot(innerRender.vnodes, arraySlot);
				} else {
					insertObjectSlot(innerRender.vnodes, objectSlot);
				}
			}

			if (comment) {
				var dom = avalon.vdom(vdom, 'toDOM');
				comment.parentNode.replaceChild(dom, comment);
				comVm.$element = innerRender.root.dom = dom;
				delete this.reInit;
			}

			//处理DOM节点

			dumpTree(vdom.dom);
			comVm.$element = vdom.dom;
			groupTree(vdom.dom, vdom.children);
			if (fromCache) {
				fireComponentHook(comVm, vdom, 'Enter');
			} else {
				fireComponentHook(comVm, vdom, 'Ready');
			}
		},
		diff: function diff(newVal, oldVal) {
			if (cssDiff.call(this, newVal, oldVal)) {
				return true;
			}
		},

		update: function update(vdom, value) {
			//this.oldValue = value //★★防止递归

			switch (this.readyState) {
				case 0:
					if (this.reInit) {
						this.init();
						this.readyState++;
					}
					break;
				case 1:
					this.readyState++;
					break;
				default:
					this.readyState++;
					var comVm = this.comVm;
					avalon.viewChanging = true;
					avalon.transaction(function () {
						for (var i in value) {
							if (comVm.hasOwnProperty(i)) {
								if (Array.isArray(value[i])) {
									comVm[i] = value[i].concat();
								} else {
									comVm[i] = value[i];
								}
							}
						}
					});

					//要保证要先触发孩子的ViewChange 然后再到它自己的ViewChange
					fireComponentHook(comVm, vdom, 'ViewChange');
					delete avalon.viewChanging;
					break;
			}
			this.value = avalon.mix(true, {}, value);
		},
		beforeDispose: function beforeDispose() {
			var comVm = this.comVm;
			if (!this.cacheVm) {
				fireComponentHook(comVm, this.node, 'Dispose');
				comVm.$hashcode = false;
				delete avalon.vmodels[comVm.$id];
				this.innerRender && this.innerRender.dispose();
			} else {
				fireComponentHook(comVm, this.node, 'Leave');
			}
		}
	});

	function replaceRoot(instance, innerRender) {
		instance.innerRender = innerRender;
		var root$$1 = innerRender.root;
		var vdom = instance.node;
		var slot = vdom.props.slot;
		for (var i in root$$1) {
			vdom[i] = root$$1[i];
		}
		if (vdom.props && slot) {
			vdom.props.slot = slot;
		}
		innerRender.root = vdom;
		innerRender.vnodes[0] = vdom;
	}

	function fireComponentHook(vm, vdom, name) {
		var list = vm.$events['on' + name];
		if (list) {
			list.forEach(function (el) {
				setTimeout(function () {
					el.callback.call(vm, {
						type: name.toLowerCase(),
						target: vdom.dom,
						vmodel: vm
					});
				}, 0);
			});
		}
	}

	function createComponentVm(component, value, is) {
		var hooks = [];
		var defaults = component.defaults;
		collectHooks(defaults, hooks);
		collectHooks(value, hooks);
		var obj = {};
		for (var i in defaults) {
			var val = value[i];
			if (val == null) {
				obj[i] = defaults[i];
			} else {
				obj[i] = val;
			}
		}
		obj.$id = value.id || value.$id || avalon.makeHashCode(is);
		delete obj.id;
		var def = avalon.mix(true, {}, obj);
		var vm = avalon.define(def);
		hooks.forEach(function (el) {
			vm.$watch(el.type, el.cb);
		});
		return vm;
	}

	function collectHooks(a, list) {
		for (var i in a) {
			if (componentEvents[i]) {
				if (typeof a[i] === 'function' && i.indexOf('on') === 0) {
					list.unshift({
						type: i,
						cb: a[i]
					});
				}
				//delete a[i] 这里不能删除,会导致再次切换时没有onReady
			}
		}
	}

	function resetParentChildren(nodes, arr) {
		var dir = arr && arr[0] && arr[0].forDir;
		if (dir) {
			dir.parentChildren = nodes;
		}
	}

	function insertArraySlot(nodes, arr) {
		for (var i = 0, el; el = nodes[i]; i++) {
			if (el.nodeName === 'slot') {
				resetParentChildren(nodes, arr);
				nodes.splice.apply(nodes, [i, 1].concat(arr));
				break;
			} else if (el.children) {
				insertArraySlot(el.children, arr);
			}
		}
	}

	function insertObjectSlot(nodes, obj) {
		for (var i = 0, el; el = nodes[i]; i++) {
			if (el.nodeName === 'slot') {
				var name = el.props.name;
				resetParentChildren(nodes, obj[name]);
				nodes.splice.apply(nodes, [i, 1].concat(obj[name]));
				continue;
			} else if (el.children) {
				insertObjectSlot(el.children, obj);
			}
		}
	}

	avalon.components = {};
	avalon.component = function (name, component) {

		component.extend = componentExtend;
		return addToQueue(name, component);
	};
	function addToQueue(name, component) {
		avalon.components[name] = component;
		for (var el, i = 0; el = componentQueue[i]; i++) {
			if (el.is === name) {
				componentQueue.splice(i, 1);
				el.reInit = true;
				delete el.value;
				el.update();
				i--;
			}
		}
		return component;
	}

	function componentExtend(child) {
		var name = child.displayName;
		delete child.displayName;
		var obj = { defaults: avalon.mix(true, {}, this.defaults, child.defaults) };
		if (child.soleSlot) {
			obj.soleSlot = child.soleSlot;
		}
		obj.template = child.template || this.template;
		return avalon.component(name, obj);
	}

	return avalon;
});